Является ли структура присваивания атомарным в C / C ++? - PullRequest
15 голосов
/ 31 марта 2011

Я пишу программу, в которой один процесс читает и записывает в общую память, а другой - только чтение.В разделяемой памяти есть такая структура:


struct A{
    int a;
    int b;
    double c;
};

, что я ожидаю, это прочитать структуру сразу, потому что во время чтения другой процесс может изменять содержимое структуры.Это может быть достигнуто, если присвоение структуры является атомарным, что не прерывается.Примерно так:


struct A r = shared_struct;

Итак, является ли структура присваивания атомарной в C / C ++?Я пытался найти в Интернете, но не могу найти полезных ответов.Кто-нибудь может помочь?Спасибо.

Ответы [ 4 ]

17 голосов
/ 31 марта 2011

Нет, и C, и C ++ стандарт не гарантируют, что операции присваивания будут атомарными. Для этого вам понадобятся специфические для реализации вещи - что-то в компиляторе или в операционной системе.

9 голосов
/ 31 марта 2011

C и C ++ поддерживают атомарные типы в их текущих стандартах.

В C ++ 11 появилась поддержка атомарных типов . Точно так же в C11 введено атомика .

2 голосов
/ 06 сентября 2017

Вам нужно сделать атомарный снимок всех членов структуры?Или вам просто нужен общий доступ на чтение / запись к отдельным членам по отдельности?Последнее намного проще, см. Ниже.


C11 stdatomic и C ++ 11 std :: atomic обеспечивают синтаксис для атомарных объектов произвольных размеров,Но если они больше, чем 8B или 16B, они не будут свободны от блокировки в типичных системах.(т.е. атомарная загрузка, хранение, обмен или CAS будут реализованы путем взятия скрытой блокировки и последующего копирования всей структуры).

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

Или добавляет уровень косвенности , так что есть указатель, который можно легко атомарно обновить, чтобы он указывал на другойstruct с другим набором значений. Это строительный блок для RCU (чтение-копирование-обновление) См. Также https://lwn.net/Articles/262464/. Существуют хорошие реализации библиотеки RCU, поэтому используйте один вместо того, чтобы свернуть свой собственныйесли ваш вариант использования намного проще, чем в общем случае.Выяснить, когда освобождать старые копии структуры, - одна из трудных частей, потому что вы не можете сделать это, пока последний поток чтения не закончится с этим.И весь смысл RCU состоит в том, чтобы сделать путь чтения как можно более легким ...


Ваша структура составляет 16 байтов в большинстве систем;едва достаточно маленький, чтобы x86-64 мог загружать или хранить все вещи несколько более эффективно, чем просто блокировка.(Но только с lock cmpxchg16b).Тем не менее, не совсем глупо использовать атомарность C / C ++ для этой

, общей для C ++ 11 и C11:

struct A{
    int a;
    int b;
    double c;
};

В C11 используйте тип _Atomicклассификатор для создания атомарного типа .Это такой классификатор, как const или volatile, поэтому вы можете использовать его практически для всего.

#include <stdatomic.h>
_Atomic struct A shared_struct;

  // atomically take a snapshot of the shared state and do something
double read_shared (void) {
    struct A tmp = shared_struct;   // defaults to memory_order_seq_cst
    // or atomic_load_explicit(&shared_struct, &tmp, memory_order_relaxed);

    //int t = shared_struct.a;  // UNDEFINED BEHAVIOUR

    // then do whatever you want with the copy, it's a normal struct
    if (tmp.a > tmp.b)
       tmp.c = -tmp.c;
    return tmp.c;
}

// or take tmp by value or pointer as a function arg
// static inline
void update_shared(int a, int b, double c) {
    struct A tmp = {a, b, c};
    //shared_struct = tmp;
    // If you just need atomicity, not ordering, relaxed is much faster for small lock-free objects (no memory barrier)
    atomic_store_explicit(&shared_struct, tmp, memory_order_relaxed);
}

Обратите внимание, что доступ к одному члену структуры _Atomic - неопределенное поведение.Он не будет уважать блокировку и не может быть атомарным.Так что не делайте int i = shared_state.a; (C ++ 11 не компилирует это, но C11 будет).

В C ++ 11 это почти то же самое : используйте std::atomic<T> template.

#include <atomic>
std::atomic<A> shared_struct;

  // atomically take a snapshot of the shared state and do something
double read_shared (void) {
    A tmp = shared_struct;   // defaults to memory_order_seq_cst
    // or A tmp = shared_struct.load(std::memory_order_relaxed);
    // or atomic_load_explicit(&shared_struct, &tmp, memory_order_relaxed);

    //int t = shared_struct.a;  // won't compile: no operator.() overload

    // then do whatever you want with the copy, it's a normal struct
    if (tmp.a > tmp.b)
       tmp.c = -tmp.c;
    return tmp.c;
}

void update_shared(int a, int b, double c) {
    struct A tmp{a, b, c};
    //shared_struct = tmp;
    // If you just need atomicity, not ordering, relaxed is much faster for small lock-free objects (no memory barrier)
    shared_struct.store(tmp, std::memory_order_relaxed);
}

Посмотрите его в проводнике компилятора Godbolt


Если вам не нужен снимок всей структуры, а вместо этого просто хотите, чтобы каждый член был отдельно атомарный , тогда вы можете просто сделать каждый член атомным типом.(Например, atomic_int и _Atomic double или std::atomic<double>).

struct Amembers {
    atomic_int a, b;
#ifdef __cplusplus
    std::atomic<double> c;
#else
    _Atomic double c;
#endif
} shared_state;
// If these members are used separately, put them in separate cache lines
// instead of in the same struct to avoid false sharing cache-line ping pong.

(Обратите внимание, что стандарт C11 stdatomic не гарантированно совместим с C ++ 11 std :: atomicпоэтому не ожидайте, что сможете получить доступ к той же самой структуре из C или C ++.)

В C ++ 11 struct-assignment для структуры с атомарными членами не будет компилироваться, потому что std::atomicудаляет свой экземпляр-конструктор.(Вы должны загрузить std::atomic<T> shared в T tmp, как в приведенном выше примере с целой структурой.)

В C11, struct-assignment для неатомарной структуры с атомарными членами будет компилироваться, но не является атомным .Стандарт C11 нигде конкретно не указывает на это.Лучшее, что я могу найти: n1570: 6.5.16.1 Простое назначение:

В простом назначении (=) значение правого операнда преобразуется в тип выражения назначения и заменяет значениехранится в объекте, обозначенном левым операндом.

Поскольку это ничего не говорит о специальной обработке атомарных членов, следует предположить, что это похоже на memcpy представлений объекта.(За исключением того, что разрешено не обновлять заполнение.)

На практике легко заставить gcc генерировать asm для структур с атомарным элементом, где он копирует неатомно.Особенно с большим атомарным элементом, который является атомным, но не свободным от блокировки.

1 голос
/ 31 марта 2011

Нет, это не так.

Это на самом деле является свойством архитектуры ЦП по отношению к размеченной памяти

. Вы можете использовать решение «подстановка атомных указателей», котороеможет быть атомарным и использоваться в сценарии без блокировки. Обязательно пометьте соответствующий общий указатель (переменные) как изменчивый, если важно, чтобы изменения были замечены другими потоками «немедленно» Этого в реальной жизни (TM) недостаточно, чтобы гарантировать правильностьлечение компилятором.Вместо этого программируйте непосредственно против атомарных примитивов / встроенных функций, когда вы хотите иметь семантику без блокировки . (см. Комментарии и связанные статьи для справки)

Конечно, наоборот, вам нужно будет сделать глубокую копию в соответствующее время, чтобы выполнить обработку чтениясторона этого.

Теперь все это быстро становится очень сложным в отношении управления памятью, и я предлагаю вам внимательно изучить свой дизайн и спросить себя серьезно , оправдывают ли все (предполагаемые?) преимущества в производительностириск.Почему бы вам не выбрать простую блокировку (для чтения / записи) или получить в свое распоряжение необычную реализацию с указателем общего доступа, которая является поточно-ориентированной?

...