Атомная нагрузка в C с MSVC

97
9

TL; DR: Мне нужен эквивалент C11 atomic_load Microsoft C (не С++). Кто-нибудь знает, что такое правильная функция?


У меня есть довольно стандартный код, который использует атомы. Что-то вроде


do {
bar = atomic_load(&foo);
baz = some_stuff(bar);
} while (!atomic_compare_exchange_weak(&foo, &bar, baz));

Я пытаюсь понять, как обращаться с MSVC. CAS достаточно прост (InterlockedCompareExchange), но atomic_load оказывается более неприятным.


Возможно, мне что-то не хватает, но Список функций синхронизации в MSDN, похоже, не имеет ничего для простой загрузки. Единственное, что я могу придумать, это что-то вроде InterlockedOr(object, 0), которое создало бы хранилище для каждой загрузки (не говоря уже о заборе)...


Пока переменная является volatile, я думаю, что было бы безопасно просто прочитать значение, но если я сделаю эту Visual Studio анализ кода выделяет кучу C28112 предупреждений ( "переменная (foo), к которой осуществляется доступ через функцию блокировки, всегда должна быть доступна через функцию блокировки." ).


Если простое чтение - это действительно правильный путь, я думаю, что могу заставить замолчать тех, у кого что-то вроде


#define atomic_load(object) \
__pragma(warning(push)) \
__pragma(warning(disable:28112)) \
(*(object)) \
__pragma(warning(pop))

Но настойчивость анализатора, что я всегда должен использовать функции Interlocked*, заставляет меня думать, что должен быть лучший способ. Если это так, что это такое?

спросил(а) 2021-01-19T14:46:45+03:00 2 месяца, 4 недели назад
1
Решение
61

Я думаю, что игнорирование анализатора здесь приемлемо, учитывая в документации говорится, что простые чтения переменных ширины регистров являются безопасными (32-разрядный бит на 32 бит систем, 64-битных в 64-битных системах). Сама документация по предупреждению в основном говорит о чрезмерной осторожности, даже когда доступ может быть безопасным.


Тем не менее, если вы хотите закрыть его, вы всегда можете использовать операцию idempotent Interlocked для получения желаемого поведения. Например, вы можете просто определить:


#define atomic_load(object) InterlockedOr((object), 0)

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


Если вы моделировали atomic_load_explicit с помощью memory_order_relaxed, вы могли бы повысить производительность, используя InterlockedOrNoFence, чтобы избежать барьеров памяти, но для имитации стандартного (последовательно согласованного) atomic_load, который вы хотите придерживаться с InterlockedOr.

InterlockedOr был выбран в основном произвольно (по теории, что он может быть немного быстрее в оборудовании, чем операция с переносом, например сложение или вычитание), но InterlockedXor с 0 будет вести себя одинаково, как и несколько других операций, если они были сделаны с их идентификационной ценностью.


Вы также можете использовать InterlockedCompareExchange аналогичным образом; вам потребуется тестирование, чтобы определить, что было быстрее:


#define atomic_load(object) InterlockedCompareExchange((object), 0, 0)

где снова, если значение уже равно 0, вы устанавливаете его на ноль, но все, что вы действительно используете для него, это получить возвращаемое значение, исходное значение перед обменом no-op.

ответил(а) 2021-01-19T14:46:45+03:00 2 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема