Производит ли неатомарное изменение структуры через _Atomic указатель гонку данных? - PullRequest
2 голосов
/ 13 апреля 2019

Я пытаюсь понять, как работает модель памяти C11, и написал две функции, содержащие выражения, conflict (в смысле 5.1.2.4(p4)):

struct my_struct{
    uint64_t first;
    int64_t second;
} * _Atomic instance;

void* set_first(void *ignored){
    uint64_t i = 0;
    while(1){
        struct my_struct *ms = atomic_load_explicit(&instance, memory_order_acquire);
        ms -> first = i++;
        atomic_store_explicit(&instance, ms, memory_order_release);
        sleep(1);
    }
}

void* print_first(void *ignored){
    while(1){
        struct my_struct *ms = atomic_load_explicit(&instance, memory_order_acquire);
        uint64_t current = ms -> first;
        char buf[100];
        memset(buf, '\0', sizeof(buf));
        sprintf(buf, "%" PRIu64 "\n", current);
        fputs_unlocked(buf, stdout);
        sleep(2);
    }
}

И основная функция:

int main(void){
    struct my_struct tmp = {.first = 0, .second = 0};
    atomic_init(&instance, &tmp);
    printf("main\n");
    pthread_t set_thread;
    pthread_create(&set_thread, NULL, &set_first, NULL);

    pthread_t print_thread;
    pthread_create(&print_thread, NULL, &print_first, NULL);
    while(1){
        sleep(100);
    }
}

Итак, я попытался доказать, содержит ли программа нет гонок данных .Вот мои мысли:

  1. Мы знаем, что операция освобождения атомарного объекта синхронизируется с операцией получения на объекте.Так atomic_store_explicit(&instance, ms, memory_order_release); в set_first синхронизируется с atomic_load_explicit(&instance, memory_order_acquire) в print_first.

  2. Поскольку побочный эффект ms -> first = i++ в функции set_first появился до atomic_store_explicit(&instance, ms, memory_order_release); в тексте программы, я предположил, что это sequenced-before это (Это я не уверен, не смог найти никаких нормативных ссылок).

  3. Объединение пуль 1. и 2. подразумевает, что ms -> first = i++ inter-thread происходит до atomic_load_explicit(&instance, memory_order_acquire);, поэтому они в происходят до отношений.

  4. Применяя определение гонки данных, мы заключаем, что конфликтующие действия uint64_t current = ms -> first; в функции print_first и ms -> first = i++; в функции set_first не создают гонки данных.

Таким образом, поведение кажется вполне определенным.

Сомнительно предположить, что ms -> first = i++; упорядочено до atomic_store_explicit(&instance, ms, memory_order_release);, потому что они произошли один перед другим втекст программы.

Это правильно или программа содержит данные гонки?

1 Ответ

3 голосов
/ 13 апреля 2019

Изменение неатомарных объектов с помощью указателя не _Atomic не является по своей природе гонкой данных UB. (например, у вас мог бы быть алгоритм, который int *p = shared_ptr++; мог бы заставить каждый поток захватывать свой собственный слот в неатомарном массиве.)

Но в этом случае у вас есть явный случай UB, потому что у вас есть 2 потока, обращающихся к основному tmp.first, и они оба не читают.


Магазин с mo_release упорядочен после всех предыдущих хранилищ (и загрузок), включая неатомарные хранилища, такие как ms->first = .... В этом суть релиз-магазинов против расслабленных.

Но недостаток в ваших рассуждениях заключается в шаге 1: atomic_store_explicit(&instance, ms, memory_order_release) в set_first только синхронизируется с получает нагрузки, которые видят сохраненное значение! Получает нагрузки в других потоках волшебным образом не ждите релиз магазина, который еще не произошел. Гарантия заключается в том, что если / когда вы do загрузите значение, хранящееся в хранилище релизов 1 , вы также увидите все более ранние вещи из этого потока.

Если загрузка получения происходит перед выпуском хранилища (в глобальном порядке, если он есть), синхронизация с ним отсутствует.

Приобретение-загрузка в обоих потоках может происходить одновременно , и тогда лиса оказывается в курятнике: ms -> first = i++; и uint64_t current = ms -> first; работают без синхронизации.

Совершенно неважно, что записывающий поток позже сделает хранилище релизов, чтобы сохранить то же значение обратно в instance.


Сноска 1: (Язык "release-sequence" в стандарте расширяет это, чтобы увидеть результат операции RMW, которая изменила результат начального release-store, и т. Д.)


Что касается других потоков, то atomic_load_explicit в set_first в основном не имеет значения. С тем же успехом можно вытащить его из петли.

...