Pull to refresh

Interlocked-функции — примитивная и эффективная синхронизация в Windows

Поскольку речь идет о Windows, то одним из основных источников есть MSDN, на который я постоянно буду давать ссылки на писание функций, ну и книга Джеффри Рихтера «Windows для профессионалов». В статье рассказаны основы основ синхронизации многопоточных приложений с коротким описаний функций WinAPI.

Способы синхронизация разделяются на две группы: синхронизация в пользовательском режиме и с использованием объектов ядра. Синхронизировать в пользовательском режиме можно двумя способами: использую семейство Interlocked-функций либо критические секции. Их преимуществом есть быстрота(один переходит из пользовательского режмимо в режим ядра занимает около 1000 тактов процессора), но именно из-за этого они способны решать очень ограниченный круг задач. Универсальным советом есть использовать синхронизацию объектами ядра только в том случае, если поставленную задачу нельзя решить с помощью Interlocked-функций или критических секций.

В статье пойдет разговор именно про Interlocked-функции, которые доступны разработчикам под ОС Windows.


Семейство Interlocked-функций

Используются в случае, если разным потокам необходимо изменять одну и ту же переменную. Например, увеличивать счетчик числа операций.
Код вида var++; компилятором генерируется в что-то вроде

MOV EAX, [var]; 1
INC EAX; 2
MOV [var], EAX; 3


Соответственно, если будут два потока с таким кодом, то нельзя гарантировать, что выполняться строки 1,2,3 первого потока, а потом 1,2,3 второго, первая строчка второго потока может выполнится до того, как первый запишет новое значение в переменную, и тогда второй поток изменит старое значение и перетрет изменения первого. Использование Interlocked-функций гарантирует, что операция будет выполнена атомарно, и описанная выше проблема отпадает. При использование процессоров х86 они выдают по шине аппаратный сигнал, не давая другим процессорам процессорам обратится по тому же адресу памяти на время работы функции.
В примере с инкрементом желанного результат можно достичь двумя функциями – InterlockedExchangeAdd(&var, 1), которая возвратит начальное значение var, либо InterlockedIncrement(&var), которая возвращает уже инкрементированое значение переменной. Также существуют их 64-разрядные версии — InterlockedIncrement64 и InterlockedExchangeAdd64.
Существуют ещё функции, которые реализуют:
  • декремент: LONG InterlockedDecrement(LONG volatile *Addend), возвращает значение декрементированной переменной и 64-битная версия — InterlockedDecrement64
  • монопольную замены текущего значения: LONG InterlockedExchange(LONG volatile *Target, LONG Value), которая всегда оперирует 32-разрядными значениямии возвращает начальное значение параметра Target, и PVOID InterlockedExchangePointer с такой же сигнатурой, которая в случае 64-разрядной программе будет оперировать с 64-разрядными значениями, возвращает указатель на Target
  • логические операции: логическое «И» LONG InterlockedAnd(LONG volatile *Destination, LONG Value), логическое «ИЛИ» InterlockedOr и сумму по модулю два InterlockedXor. У них одинаковая сигнатура, все они результат выполнения записывают в первый параметр и возвращают исходное значение первого параметра
  • выполнение сравнения и присвоения как атомарной операции: LONG InterlockedCompareExchange(LONG volatile *Destination, LONG Exchange, LONG Comparand) сравнивает значение первых двух параметров, и если они совпадают, записывает в первый параметр третий. Возвращает исходное значение первого параметра и работает всегдда з 32-разрядными значениями. PVOID InterlockedCompareExchangePointer(PVOID volatile *Destination, PVOID Exchange, PVOID Comparand) делает тоже самое, отличие в том, что в 64-разрядном приложение оперирует с 64-разрядными значениями. Также существует InterlockedCompare64Exchange128, которая тоже работает так же, сравнивает два 64-битных значения, а третий параметр у неё имеет разрядность 128 бит


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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.