Компилятор памяти и мьютекса

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 в тот же регистр, чтобы можно было получить новое местоположение. Если между этими обращениями существует вызов функции, компилятор не может этого сделать.