Почему оба условия? Потому что второй поток также получит блокировку в этом случае. (Изменить: но этот случай не может произойти, если все потоки следуют протоколу спин-блокировки.)
Если блокировка доступна (сигнализируется), m_s
имеет значение 1. При получении каким-либо потоком оно имеет значение 0. Другие значения не допускаются.
Рассмотрим один поток, которому нужна блокировка, независимо от того, сигнализирована она или нет в тот момент, когда поток с именем Enter()
не важен. Разрешается брать блокировку, если m_s
равен 1, и для этого требуется изменить ее на 0. Первая итерация, в которой это происходит, приводит к выходу из цикла и блокировке потока.
Теперь рассмотрим два потока, которые хотят одну и ту же блокировку. Оба вызывают TestAndSet()
, ожидая, чтобы значение 1 стало 0. Поскольку функция TestAndSet()
является атомарной, только один из ожидающих потоков когда-либо сможет увидеть значение 1. Все остальные потоки только когда-либо видят m_s
как 0, и должен продолжать ждать.
Условие, где m_s
равно 1 после установки его в 0 в этом потоке, означает, что какой-то другой поток сигнализировал между атомарной операцией и условием. Поскольку предполагается, что только один поток за раз имеет блокировку, кажется, что этого не должно произойти , что не может произойти.
Я предполагаю, что это попытка удовлетворить неизменное обещание спин-блокировки. (Изменить: я больше не уверен, подробнее ниже ...) Если оно удерживается, значение m_s
должно быть равно нулю. Если нет, то это один. Если установка его в ноль не «прилипает», то происходит что-то странное, и лучше не предполагать, что теперь он удерживается этим потоком, когда инвариант неверен.
Редактировать: Джон Скит отмечает, что этот случай может быть недостатком в первоначальной реализации. Я подозреваю, что он прав.
Состояние гонки, которое охраняется, относится к нити, которую не имеет права сигнализировать о спин-блокировке , сигнализируя о спин-блокировке в любом случае. Если вы не можете доверять вызывающим абонентам следовать правилам, то, в конце концов, спинлоки не являются предпочтительным методом синхронизации.
Редактировать 2: Предлагаемая ревизия выглядит намного лучше. Это явно позволило бы избежать взаимодействия когерентности многоядерного кэша, которое было у оригинала из-за того, что он всегда записывал сторожа m_s
.
После прочтения о протоколе TATAS (вы можете узнавать что-то новое каждый день, если вы обратите внимание ...) и о проблеме когерентности многоядерного кэша, на которую он обращается, мне становится ясно, что оригинал Код пытался сделать что-то подобное, но не понимая тонкости этого. Действительно, было бы безопасно (при условии, что все вызывающие абоненты следуют правилам) сбросить избыточную проверку на m_s
, как она была написана. Но код должен был записывать в m_s
на каждой итерации цикла, и это привело бы к хаосу в реальном многоядерном чипе с кэшированием на ядро.
Новый спин-блокировка все еще уязвима для второго потока, освобождающего его без удержания. Нет способа починить это. Мои предыдущие заявления о доверии вызывающих абонентов к соблюдению протокола все еще применяются.