Компилятор памяти и мьютекса
posix standard говорит, что такие вещи, как мьютекс, будут обеспечивать синхронизацию памяти.
Однако компилятор может изменить порядок доступа к памяти.
Скажем,
lock(mutex);
setdata(0);
ready = 1;
unlock(mutex);
Он может быть изменен на следующий код путем переупорядочения компилятора, правильно?
ready = 1;
lock(mutex);
setdata(0);
unlock(mutex);
Итак, как mutex может синхронизировать доступ к памяти? Чтобы быть более точным, как компиляторы знают, что переупорядочение не должно происходить через блокировку/разблокировку?
на самом деле здесь для аспекта с одним потоком, готовый переупорядоченный присваивание полностью безопасен, поскольку готовый не используется в блокировке функций (mutex).
Редакция:
Поэтому, если вызов функции - это то, что компилятор не получит,
мы можем рассматривать это как барьер памяти компилятора, например
asm volatile("" ::: "memory")
Ответы
Ответ 1
Общий ответ заключается в том, что ваш компилятор должен поддерживать POSIX, если вы хотите использовать его для целей POSIX, и эта поддержка означает, что он должен знать, чтобы избежать переупорядочения через блокировку и разблокировку.
Таким образом, такие знания обычно достигаются тривиальным образом: компилятор не переупорядочивает доступ к (не доказуемо-локальным) данным через вызов внешней функции, которая может их использовать или модифицировать. Он должен был знать что-то особенное о lock
и unlock
, чтобы иметь возможность изменять порядок.
И нет, это не так просто, как "вызов глобальной функции всегда является барьером для компилятора" - мы должны добавить "если компилятор не знает что-то конкретное в этой функции". Это действительно так: например. pthread_self
в Linux (NPTL) объявляется с атрибутом __const__
, позволяя gcc
переупорядочивать вызовы pthread_self()
, даже полностью исключая ненужные вызовы.
Мы можем легко представить компилятор, поддерживающий атрибуты функции для семантики получения/выпуска, делая lock
и unlock
меньше, чем полный барьер компилятора.
Ответ 2
Составители не будут изменять порядок вещей, когда не ясно, что это безопасно. В вашем примере "что, если" вы не предлагаете переупорядоченный доступ к памяти, вы спрашиваете, что, если компилятор полностью изменит порядок кодирования - и это не произойдет. Что-то, что может сделать компилятор, это изменить порядок чтения/записи в реальном времени, но не вызовы функций (с учетом или без учета этих обращений к памяти).
Пример того, где компилятор может изменить порядок доступа к памяти... позволяет сказать, что у вас есть этот код:
a = *pAddressA;
b = *pAddressB;
и рассмотрим случай, когда значение pAddressB
находится в регистре, а pAddressA
- нет. Это справедливая игра для компилятора, чтобы сначала прочитать адрес B, а затем переместить значение pAddressA
в тот же регистр, чтобы можно было получить новое местоположение. Если между этими обращениями существует вызов функции, компилятор не может этого сделать.