Достаточны ли функции блокировки мьютекса без энергозависимости? - PullRequest
36 голосов
/ 27 июля 2011

Мы с коллегой пишем программное обеспечение для различных платформ, работающих на x86, x64, Itanium, PowerPC и других 10-летних серверных процессорах.

Мы только что обсудили, достаточно ли самих мьютекс-функций, таких как pthread_mutex_lock () ... pthread_mutex_unlock (), или защищенная переменная должна быть изменчивой.

int foo::bar()
{
 //...
 //code which may or may not access _protected.
 pthread_mutex_lock(m);
 int ret = _protected;
 pthread_mutex_unlock(m);
 return ret;
}

Меня беспокоит кеширование. Может ли компилятор поместить копию _protected в стек или в регистр и использовать это устаревшее значение в присваивании? Если нет, что мешает этому случиться? Уязвимы ли варианты этого паттерна?

Я предполагаю, что компилятор на самом деле не понимает, что pthread_mutex_lock () является специальной функцией, поэтому мы просто защищены точками последовательности?

Большое спасибо.

Обновление: хорошо, я вижу тенденцию с ответами, объясняющими, почему изменчивый вреден. Я уважаю эти ответы, но статьи на эту тему легко найти в Интернете. То, что я не могу найти в Интернете, и причина, по которой я задаю этот вопрос, это то, как я защищен без изменчивой. Если приведенный выше код верен, как неуязвим ли он для проблем с кэшированием?

Ответы [ 4 ]

14 голосов
/ 27 июля 2011

Самый простой ответ: volatile вообще не нужен для многопоточности.

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

C ++ 0x имеет концепцию потоков и безопасности потоков, но текущий стандарт этого не делает, и поэтому volatile иногда ошибочно идентифицируется как нечто, что предотвращает переупорядочение операций и доступ к памяти для многопоточного программирования, когда оно никогда не предназначалось и не может быть надежно использован таким образом.

Единственное, что volatile следует использовать в C ++, это разрешить доступ к отображенным в память устройствам, разрешить использование переменных между setjmp и longjmp и разрешить использование sig_atomic_t переменных в обработчиках сигналов. Само ключевое слово не делает переменную атомарной.

Хорошая новость в C ++ 0x - у нас будет конструкция STL std::atomic, которую можно использовать для гарантии атомарных операций и поточно-безопасных конструкций для переменных. Пока ваш выбранный компилятор не поддержит его, вам, возможно, придется обратиться к библиотеке надстройки или запустить некоторый ассемблерный код, чтобы создать свои собственные объекты для предоставления атомарных переменных.

P.S. Большая путаница вызвана тем, что Java и .NET фактически применяют многопотоковую семантику с ключевым словом volatile C ++, однако следует примеру C, где это не так.

9 голосов
/ 27 июля 2011

Если приведенный выше код верен, как он неуязвим для кеширования? вопросы?

До C ++ 0x это не так. И это не указано в C. Итак, это действительно зависит от компилятора. В общем, если компилятор не гарантирует, что он будет соблюдать ограничения порядка доступа к памяти для функций или операций, которые включают в себя несколько потоков, вы не сможете писать многопоточный безопасный код с этим компилятором. См., Что Hans J Boehm's не может быть реализован как библиотека .

Что касается абстракций, которые ваш компилятор должен поддерживать для потокаобезопасного кода, то запись в википедии о Memory Barriers является довольно хорошей отправной точкой.

(Что касается того, почему люди предложили volatile, некоторые компиляторы рассматривают volatile как барьер памяти для компилятора. Это определенно не стандарт.)

8 голосов
/ 27 июля 2011

Ваша библиотека потоков должна включать соответствующие барьеры ЦП и компилятора при блокировке и разблокировке мьютекса. Для GCC memory clobber в операторе asm действует как барьер компилятора.

На самом деле есть две вещи, которые защищают ваш код от кеширования (компилятора):

  • Вы вызываете не чистую внешнюю функцию (pthread_mutex_*()), что означает, что компилятор не знает, что эта функция не изменяет ваши глобальные переменные, поэтому он должен перезагрузить их.
  • Как я уже сказал, pthread_mutex_*() включает в себя барьер компилятора, например: на glibc / x86 pthread_mutex_lock() в итоге вызывает макрос lll_lock(), который имеет memory clobber, заставляя компилятор перезагрузить переменные.
3 голосов
/ 27 июля 2011

Ключевое слово volatile - это подсказка компилятору, что переменная может измениться вне логики программы, такой как отображаемый в памяти аппаратный регистр, который может измениться как часть подпрограммы обработки прерывания. Это препятствует тому, чтобы компилятор предполагал, что кэшированное значение всегда корректно и обычно вынуждает чтение памяти для получения значения. Такое использование предшествует созданию потоков на пару десятилетий или около того. Я видел, что он также используется с переменными, управляемыми сигналами, но я не уверен, что использование было правильным.

Переменные, защищаемые мьютексами, гарантированно верны при чтении или записи разными потоками. API потоков необходим для обеспечения согласованности таких представлений переменных. Этот доступ является частью логики вашей программы, и ключевое слово volatile здесь не имеет значения.

...