2016-02-11 4 views
4

Каковы преимущества использования специально созданного спинлока (например http://anki3d.org/spinlock) по сравнению с кодом, как это:SpinLock против станд :: мьютекс :: try_lock

std::mutex m; 
while (!m.try_lock()) {} 
# do work 
m.unlock(); 
+2

Почему не просто 'm.lock();'? Какой смысл оживленного ожидания? –

+0

В этом случае у меня много незанятых ядер и вы хотите свести к минимуму задержку, в которой каждый поток начинает работать. Чувствовалось, что спин-блокировка была тем, что мне нужно, но я не понимал, почему использование мьютекса для этого не было распространено, и я продолжал видеть различные домашние проекты спинлоков. – user2411693

+2

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

ответ

5

на типичных аппаратных средствах, массивные преимущества:

  1. Ваш наивный «поддельный спин-блок» может насыщать внутренние шины ЦП, когда процессор вращается, голодая на других физических ядрах, включая физическое ядро, которое удерживает замок.

  2. Если процессор поддерживает гиперпоточность или что-то подобное, ваш наивный «поддельный спин-блокировка» может потреблять чрезмерные ресурсы выполнения на физическом ядре, голодая другим потоком, разделяющим это физическое ядро.

  3. Ваша наивная «поддельная спин-блокировка», вероятно, выполняет посторонние операции записи, которые приводят к поведению плохого кэша. Когда вы выполняете операцию чтения-изменения-записи на процессоре x86/x86_64 (например, сравнение/обмен, который, вероятно, try_lock), он всегда записывается, даже если значение не изменяется. Эта запись заставляет строку кэша быть недействительной на других ядрах, требуя, чтобы они повторно делили ее, когда другое ядро ​​обращается к этой строке. Это ужасно, если потоки на других ядрах одновременно конкурируют за одну и ту же блокировку.

  4. Ваша наивная «поддельная спин-блокировка» плохо взаимодействует с предсказанием ветвления. Когда вы, наконец, получите блокировку, вы берете мать всех неправильно спроектированных ветвей прямо в точке, где вы блокируете другие потоки и должны выполняться как можно быстрее. Это похоже на бегун, который все накачивается и готов к запуску на стартовой линии, но затем, когда он слышит стартовый пистолет, он останавливается, чтобы отдышаться.

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

+0

Я следую за # 1, # 2 и # 4. Но я не понимаю № 3. Не могли бы вы немного разобраться. Цените, насколько решительно вы указали на это, это плохая идея. Извлеченный урок заменит все такие применения соответствующими шпиндельными затворами. – user2411693

+0

Когда вы выполняете операцию чтения-изменения-записи на процессоре x86/x86_64 (например, сравнение/обмен, который, вероятно, try_lock), он всегда записывает, даже если значение не изменяется. Эта запись заставляет строку кэша быть недействительной для других ядер, требуя, чтобы они повторно делили ее, когда другое ядро ​​обращается к этой строке. (Обратите внимание, что все они могут не применяться на каждой платформе.) –

+0

Спасибо. Это действительно полезно. Вы рекомендуете какие-либо ресурсы (бумажные или цифровые) для профилирования и оптимизации кода с учетом этих факторов? – user2411693

0

Основным преимуществом использования спин-блокировки является то, что чрезвычайно важно приобретать и выпускать, если все важные предпосылки истинны: На замке почти нет скопления.

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

Если, однако, предпосылка отсутствия конкуренции не выполняется, спин-блокировка, как правило, будет значительно ниже, поскольку она не продвинется вперед, но она по-прежнему потребляет ресурсы ЦП, как если бы она выполняла работу. При блокировке мьютекса ваш поток не потребляет ресурсы ЦП, поэтому их можно использовать для работы другого потока, или процессор может дросселировать, экономя электроэнергию. Это невозможно с помощью спин-блокировки, которая выполняет «активную работу» до тех пор, пока не удастся (или не сработает).
В худшем случае, если количество официантов больше числа ядер ЦП, то шпиндельные блоки могут вызвать огромный, что приводит к диспропорциональному влиянию производительности, поскольку потоки, которые активны и работают, ждут состояния, которое никогда не может произойти, когда они (поскольку для выпуска блокировки требуется запуск другого потока!).

С другой стороны, следует ожидать, что каждая современная реализация no-suck std::mutex уже включит крошечную спин-блокировку перед тем, как вернуться к выполнению системного вызова. Но ... хотя это разумное предположение, это не гарантируется.

Другой нетехнической причиной использования спин-блокировок в пользу std::mutex могут быть условия лицензии. Условия лицензии - плохое обоснование проектного решения, но, тем не менее, они могут быть очень реальными.
Например, настоящая реализация GCC основана исключительно на pthreads, что подразумевает, что «что-либо MinGW», использующее что-либо из стандартной библиотеки потоков, обязательно связывается с winpthreads (без альтернатив). Это означает, что вы подпадаете под действие лицензии winpthreads, что означает, что вы должны воспроизвести их сообщение об авторском праве. Для некоторых людей это разбойник.