Атомное чтение / запись значения int без дополнительной операции над самим значением int - PullRequest
6 голосов
/ 22 августа 2011

GCC предлагает хороший набор встроенных функций для атомарных операций.А на MacOS или iOS даже Apple предлагает хороший набор атомарных функций .Однако все эти функции выполняют операцию, например сложение / вычитание, логическую операцию (И / ИЛИ / XOR) или сравнение-и-установку / сравнение-и-обмен.То, что я ищу, - это способ атомарного присвоения / чтения значения int, например:

int a;
/* ... */    
a = someVariable;

Вот и все.a будет прочитан другим потоком, и важно, чтобы a имел либо старое, либо новое значение.К сожалению, стандарт C не гарантирует, что присвоение или чтение значения является атомарной операцией.Я помню, что когда-то где-то читал, что запись или чтение значения для переменной типа int гарантированно будет атомарным в GCC (независимо от размера int), но я искал везде на домашней странице GCC и не могу найти это утверждениебольше (возможно, он был удален).

Я не могу использовать sig_atomic_t, потому что sig_atomic_t не имеет гарантированного размера и может также иметь размер, отличный от int.

Так как только один потокбудет когда-либо «записывать» значение в a, в то время как оба потока будут «считывать» текущее значение a, мне не нужно выполнять сами операции атомарным способом, например:

/* thread 1 */
someVariable = atomicRead(a);
/* Do something with someVariable, non-atomic, when done */
atomicWrite(a, someVariable);

/* thread 2 */
someVariable = atomicRead(a);
/* Do something with someVariable, but never write to a */

Если бы оба потока собирались записать в a, то все операции должны быть атомарными, но в этом случае это может только тратить время процессора;и у нас очень мало ресурсов процессора в нашем проекте.До сих пор мы использовали мьютекс вокруг операций чтения / записи, равных a, и, хотя мьютекс удерживается в течение такого небольшого промежутка времени, это уже вызывает проблемы (один из потоков является потоком в реальном времени, и блокирование мьютекса вызывает егонарушить ограничения реального времени, что очень плохо).

Конечно, я мог бы использовать __sync_fetch_and_add для чтения переменной (и просто добавить «0» к ней, чтобы не изменять ее значение) и для записииспользуйте __sync_val_compare_and_swap для его записи (так как я знаю его старое значение, поэтому, передавая его, вы всегда будете обмениваться значением), но не приведет ли это к дополнительным накладным расходам?

Ответы [ 2 ]

3 голосов
/ 24 августа 2011

A __sync_fetch_and_add с аргументом 0 - действительно лучшая ставка, если вы хотите, чтобы ваша загрузка была атомарной и действовали как барьер памяти.Точно так же вы можете использовать and с 0 или or с -1, чтобы хранить 0 и -1 атомарно с барьером памяти.Для написания вы можете использовать __sync_test_and_set (на самом деле операция xchg), если достаточно барьера «приобретения», или если вы используете Clang, вы можете использовать __sync_swap (это операция xchg с полным барьером).

Однако во многих случаях это излишне, и вы можете предпочесть добавить барьеры памяти вручную.Если вам не нужен барьер памяти, вы можете использовать энергозависимую загрузку для атомарного чтения / записи переменной, которая выровнена и не шире слова:

#define __sync_access(x) (*(volatile __typeof__(x) *) &(x))

(Этот макрос является lvalue, поэтому выМожно также использовать его для магазина, как __sync_store(x) = 0).Функция реализует ту же семантику, что и форма C ++ 11 memory_order_consume, но только при двух допущениях:

  • , что ваша машина имеет когерентные кэши;если нет, вам нужен барьер памяти или глобальная очистка кэша до загрузки (или до первой из группы загрузок).

  • , что ваша машина неDEC Alpha.У Alpha была очень упрощенная семантика для изменения порядка доступа к памяти, поэтому на ней вам понадобится барьер памяти после загрузки (и после каждой загрузки в группе нагрузок).На Альфе вышеупомянутый макрос обеспечивает только семантику memory_order_relaxed.Кстати, первые версии Alpha не могли даже атомарно хранить байт (только слово, которое было 8 байтов).

В любом случае __sync_fetch_and_add будет работать.Насколько я знаю, ни одна другая машина не имитировала Альфу, поэтому ни одно из предположений не должно создавать проблем на современных компьютерах.

2 голосов
/ 23 августа 2011

Изменчивые, выровненные, размер слова чтения / записи являются атомарными на большинстве платформ. Проверка вашей сборки - лучший способ узнать, правда ли это на вашей платформе. Атомные регистры не могут генерировать почти столько же интересных структур без ожидания, сколько более сложные механизмы, такие как сравнение и своп, именно поэтому они включены.

См. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.56.5659&rank=3 для теории.

Относительно synch_fetch_and_add с аргументом 0 - это похоже на самую безопасную ставку. Если вы беспокоитесь об эффективности, профилируйте код и посмотрите, соответствуете ли вы вашим целям производительности. Возможно, вы стали жертвой преждевременной оптимизации.

...