Выход из критической области - PullRequest
0 голосов
/ 07 мая 2018

Рассмотрим несколько потоков, выполняющих одновременно следующий код:

long gf = 0;// global variable or class member

//...

if (InterlockedCompareExchange(&gf, 1, 0)==0) // lock cmpxchg
{
    // some exclusive code - must not execute in concurrent
    gf = 0; // this is ok ? or need
    //InterlockedExchange(&gf, 0); // [lock] xchg 
}

Обрабатывайте приведенный выше код как C-подобный псевдокод, который будет более или менее непосредственно переведен в сборку без обычных уступок для оптимизации компилятора, такой как повторный вывод и удаление хранилища.

Таким образом, после того, как какой-то поток получает исключительно флаг gf - для выхода из критической области достаточно записать ноль (как в gf = 0) или это необходимо заблокировать - InterlockedExchange(&gf, 0)?

Если оба в порядке, что лучше с точки зрения производительности, если предположить, что с высокой вероятностью несколько ядер одновременно вызывают InterlockedCompareExchange(&gf, 1, 0)?

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

Ответы [ 2 ]

0 голосов
/ 09 мая 2018

Связано: Spinlock with XCHG объясняет, почему вам не нужно xchg для снятия блокировки в x86 asm, просто инструкция сохранения.

Но в C ++ , вам нужно что-то более сильное, чем простая gf = 0; в простой переменной long gf. Модель памяти C / C ++ (для обычных переменных) очень слабо упорядоченный, даже при компиляции для сильно упорядоченного x86, потому что это важно для оптимизации.

Для правильного снятия блокировки вам потребуется хранилище релизов, не позволяющее операциям в критической секции вытекать из критической секции путем переупорядочения во время компиляции или выполнения с хранилищем gf=0. http://preshing.com/20120913/acquire-and-release-semantics/.

Поскольку вы используете long gf, а не volatile long gf, и вы не используете барьер памяти компилятора, ничто в вашем коде не помешает переупорядочению во время компиляции. (Хранилища x86 asm имеют семантику релиза, поэтому нам нужно беспокоиться только о переупорядочении во время компиляции.) http://preshing.com/20120625/memory-ordering-at-compile-time/


Мы получаем все, что нам нужно, как можно дешевле, используя std::atomic<long> gf; и gf.store(0, std::memory_order_release); atomic<long> без блокировки на любой платформе, которая поддерживает InterlockedExchange, AFAIK, так что вы должны быть в порядке смешивать и сочетать. (Или просто используйте gf.exchange(), чтобы взять блокировку. Если вы открываете свои собственные блокировки, имейте в виду, что вы должны зацикливаться на операции только для чтения + _mm_pause() во время ожидания блокировки, не забивайте xchg или lock cmpxchg и, возможно, задержит разблокировку. См. Блокирует манипулирование памятью с помощью встроенной сборки .

Это один из случаев, когда предупреждение в Почему целочисленное присваивание естественной выравниваемой переменной atomic на x86? , что вам нужно atomic<>, чтобы убедиться, что компилятор действительно хранит где / когда вам нужно это относится.

0 голосов
/ 08 мая 2018

gf = 0 достаточно. Нет необходимости использовать заблокированную операцию, поскольку никакой другой поток не может изменить ее значение.

Кстати, я бы использовал bts вместо cmpxchg, чтобы получить блокировку. Я не уверен, что это влияет на производительность, но это проще.

...