Я наткнулся на функцию InterlockedExchange и мне было интересно, когда мне следует использовать эту функцию. На мой взгляд, установка 32-битного значения на процессоре x86 всегда должна быть атомарной?
В случае, когда я хочу использовать функцию, новое значение не зависит от старого значения (это не операция приращения). Не могли бы вы привести пример, когда этот метод является обязательным (я не ищу InterlockedCompareExchange)
Когда следует использовать функцию Win32 InterlockedExchange?
Ответы (7)
В многопроцессорной или многоядерной машине каждое ядро имеет свой собственный кеш, поэтому каждое ядро имеет потенциально разные «представления» о том, что представляет собой содержимое системной памяти.
Механизмы синхронизации потоков обеспечивают синхронизацию между ядрами. Для получения дополнительной информации см. http://blogs.msdn.com/oldnewthing/archive/2008/10/03/8969397.aspx или Google для получения и выпуска семантики
InterlockedExchange одновременно запись и чтение — возвращает предыдущее значение.
Это необходимо для того, чтобы другой поток не записал другое значение сразу после того, как это сделали вы. Например, скажем, вы пытаетесь увеличить переменную. Вы можете прочитать значение, добавить 1, а затем установить новое значение с помощью InterlockedExchange. Значение, возвращаемое InterlockedExchange, должно совпадать со значением, которое вы первоначально прочитали, иначе другой поток, вероятно, увеличил его в то же время, и вам нужно выполнить цикл и повторить попытку.
Помимо записи нового значения, InterlockedExchange также считывает и возвращает предыдущее значение; вся эта операция атомарна. Это полезно для неблокирующих алгоритмов.
(Кстати, атомарность 32-разрядных операций записи не гарантируется. Рассмотрим, например, случай, когда запись не выровнена и пересекает границу кэша.)
Установка 32-битного значения является атомарной, но только если вы устанавливаете литерал.
b = a - это 2 операции:
mov eax,dword ptr [a]
mov dword ptr [b],eax
Теоретически может быть некоторый перерыв между первой и второй операцией.
b будет записано одной инструкцией независимо от ее предыдущего значения.
- person Fr0sT; 16.09.2016
Запись значения никогда не является атомарной по умолчанию. Когда вы записываете значение в переменную, генерируются несколько машинных инструкций. В современных вытесняющих ОС ОС может переключаться на другой поток между отдельными операциями записи.
Это еще большая проблема на многопроцессорных машинах, где несколько потоков могут выполняться одновременно и пытаться одновременно записывать в одну ячейку памяти.
Блокированные операции избегают этого, используя специальные инструкции для выполнения записи (x86 имеет специальные инструкции для таких ситуаций), которые выполняют чтение-изменение-запись в одной инструкции. Эти инструкции также блокируют шину памяти всех процессоров, чтобы ни один другой исполняемый поток не мог одновременно записывать значение.
InterlockedExchange следит за тем, чтобы изменение переменной и возврат ее исходного значения не прерывались другими потоками.
Таким образом, если «i» является целым числом, эти вызовы (взятые по отдельности) не нуждаются в InterlockedExchange вокруг «i»:
a = i;
i = 9;
i = a;
i = a + 9;
a = i + 9;
if(0 == i)
Ни одно из этих утверждений не опирается на ОБА начальное и конечное значения «i». Но эти следующие вызовы ДЕЙСТВИТЕЛЬНО нуждаются в InterlockedExchange вокруг 'i':
a = i++; //a = InterlockedExchange(&i, i + 1);
Без этого два потока, выполняющиеся через один и тот же код, могут получить одно и то же значение «i», назначенное «a», или «a» может неожиданно пропустить два или более числа.
if(0 == i++) //if(0 == InterlockedExchange(&i, i + 1))
Два потока могут оба выполнять код, который должен произойти только один раз.
и т. д.
a = InterlockedExchange(&i, i + 1);. Ваш i может быть изменен другим процессором между i + 1 и InterlockedExchange.
- person Fr0sT; 16.09.2016
ого, сколько противоречивых ответов. Трудно разобраться, кто прав, кто виноват, а какая информация вводит в заблуждение.
Я тоже не уверен в ответе, учитывая приведенные выше полуответы, но я думаю, что это работает так, я могу ошибаться, и будет интересно узнать, прав ли я:
- 32-битные операции чтения и записи ЯВЛЯЮТСЯ атомарными, но в зависимости от вашего кода это может не иметь большого значения.
- не беспокойтесь о невыровненном чтении/записи. ВСЕ 32-разрядные операции записи в 32-разрядную переменную должны быть выровнены, иначе произойдет сбой страницы машины.
- не беспокойтесь о переносе записи в конце кэшированной страницы, этого не может быть.
- Если вам нужно писать и читать в одном потоке, а вы пишете в другом потоке, вам нужно использовать InterlockedExchange. Если вы просто читаете значение в одном потоке и записываете его в другом, то вам не нужно его использовать, но эти значения могут быть непостоянными из-за многопоточности.