Функции EnterRegion()
и LeaveRegion()
реализуют критическую область, используя вещь, называемую «алгоритм Петерсона».
Теперь ключ к алгоритму Петерсона заключается в том, что когда поток читает turn
it должен получить последнее (самое последнее) значение, записанное любым потоком. То есть операции на turn
должны быть последовательными. Кроме того, запись в interested[]
в EnterRegion()
должна стать видимой для всех потоков до (или в то же время), что и запись в turn
.
Таким образом, место для размещения mfence
после turn = process ;
- чтобы поток не продолжался до тех пор, пока его запись в turn
не будет видна всем другим потокам.
Также важно убедить компилятор читать из памяти каждый раз, когда он читает turn
и interested[]
, поэтому вы должны установить их volatile
.
Если вы пишете это для x86 или x86_64, этого достаточно - потому что они обычно "хорошо себя ведут", так что:
, и установка этих volatile
также гарантирует, что компилятор не возится с порядком.
Причина использования mfence
на x86 и x86_64 в этом случае означает sh очередь записи в память, прежде чем перейти к прочитайте значение turn
. Таким образом, вся память записывает go в очередь, и через некоторое время в будущем каждая запись достигнет фактической памяти, и эффект записи станет видимым для других потоков - запись «завершена». Пишет «завершено» в том же порядке, в каком программа их делала, но задерживается. Если поток читает что-то, что он написал недавно, процессор выберет (самое последнее) значение из очереди записи. Это означает, что потоку не нужно ждать, пока запись завершится, что, как правило, хорошо. Однако это означает, что поток не читает то же значение, что любой другой поток будет читать, по крайней мере, пока запись не завершится. * * * * * * * * * * * * * * * * * * * * mfence
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *1049* делает остановку процессора до тех пор, пока все ожидающие записи не будут "завершены", поэтому любые последующие чтения будут считывать то же самое, что и любой другой поток.
Запись в interested[]
в LeaveRegion()
не требуется (на x86 / x86_64) mfence
, что хорошо, потому что mfence
является дорогостоящей операцией. Каждый поток только когда-либо пишет в свой собственный флаг interested[]
и только когда-либо читает другой. Единственное ограничение на эту запись состоит в том, что она должна , а не"завершить" после записи в EnterRegion()
(!). К счастью, x86 / x86_64 делает все записи по порядку. [Хотя, конечно, после записи в LeaveRegion()
запись в EnterRegion()
может «завершиться» до того, как другой поток прочитает флаг.]
Для других устройств вы можете захотеть, чтобы другие заборы принудительно применяли порядок чтения / записи turn
и interested[]
. Но я не претендую на то, что знаю достаточно, чтобы посоветовать ARM или POWERP C или что-нибудь еще.