Использование летучих как атомных

Если у меня есть что-то вроде этого...

volatile long something_global = 0;

long some_public_func()
{
    return something_global++;
}

Было бы разумно ожидать, что этот код не сломается (состояние гонки) при доступе с несколькими потоками? Если это не стандарт, может ли это быть разумным предположением о современных компиляторах?

ПРИМЕЧАНИЕ: ВСЕ, что я использую для этого, это атомный прирост и декремент - ничего более причудливого.

6 ответов

Решение

Нет - изменчивый не означает синхронизированный. Это просто означает, что каждый доступ будет возвращать самое актуальное значение (в отличие от копии, кэшированной локально в потоке).

Постинкремент не является атомарной операцией, это доступ к памяти, за которым следует запись в память. Чередование двух может означать, что значение фактически увеличивается только один раз.

Нет, вы должны использовать платформо-зависимый атомарный доступ. Есть несколько библиотек, которые абстрагируют их - GLib предоставляет переносимые атомарные операции, которые при необходимости возвращаются к мьютексным блокировкам, и я считаю, что Boost также предоставляет переносимые атомарные операции.

Как я недавно узнал, для действительно атомарного доступа вам нужен полный барьер памяти, который volatile не обеспечивает. Все непостоянные гарантии состоят в том, что память будет перечитываться при каждом доступе и что доступ к volatile память не будет переупорядочена. Оптимизатор может переупорядочить некоторый энергонезависимый доступ до или после энергозависимого чтения / записи - возможно, в середине вашего приращения! - так что вы должны использовать фактические атомарные операции.

Windows предоставляет InterlockedIncrementInterlockedDecrement), чтобы делать то, что вы просите.

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

Таким образом, компиляторы не будут выдавать атомарные инструкции только потому, что вы добавили volatile ключевое слово. Вам нужно прибегнуть к встроенной сборке или специфичным для компилятора расширениям (например, gcc atomic builtins).

Я рекомендую использовать библиотеку. Самый простой способ - просто взять блокировку, когда вы хотите обновить переменную. Семафоры, вероятно, будут быстрее, если они соответствуют тому, что вы делаете. Кажется, GLib обеспечивает достаточно эффективную реализацию.

Volatile просто предотвращает оптимизацию, но атомарность требует большего. В x86 инструкциям должен предшествовать префикс LOCK, в MIPS цикл RMW должен быть окружен конструкцией LL/SC, ...

Ваша проблема в том, что C не гарантирует атомарность операторов приращения, и на практике они часто не будут атомарными. Для этого вы должны использовать библиотеку, такую ​​как Windows API или встроенные функции компилятора ( GCC, MSVC).

Другие вопросы по тегам