Подтвердить что ты не робот

Являются ли функции блокировки мьютекса достаточными без летучих?

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

Мы просто обсудили, достаточно ли функции мьютекса, такие как 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;
}

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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