Вы правы в своем описании, и такая ситуация может привести к тупику.
Но чего вам не хватает, так это того, что test_and_set
должно быть атомарной операцией. Это не test
, за которым следует set
, а уникальная неразрывная операция, которая выполняет оба.
Как правило, он реализуется процессорами с инструкцией, которая 1 / запрещает выполнение не по порядку, 2 / ждет, пока конвейер и очередь памяти процессора не опустеют, 3 / считывает память в регистр и 4 / устанавливает слово памяти. Память чтения / записи не может быть прервана, обмен потоками невозможен, и доступ к памяти запрещен другим процессорам.
На процессорах risc есть аналогичный механизм. Сначала вы выполняете специальную загрузку, которая отслеживает доступ к памяти (часто называемый load locked
), и за ним следует специальное хранилище, которое не будет работать, если какой-либо доступ был сделан в ячейке памяти (store conditional
).
Таким образом, можно быть уверенным, что только один поток имеет доступ к памяти во время test_and_set
, и описанная вами ситуация не может произойти.
//lock = true initially because P0 is in critical section
P1 boolean rv = *target; //rv = true, lock = true
//P0 now completed its critical section and is ready to leave the lock
// BUT P0 MUST WAIT THE COMPLETION OF THE TAS.
// NO THREAD SWAP CAN HAPPEN AND ACCESS TO *target IS LOCKED
// DELAYED UNTIL END OF TAS P0 lock = false //rv = true, lock = false
P1 *target = true; //rv = true, lock = true
P1 return rv; // returns true
//NOW WE CAN DO
P0 lock = false //rv = true, lock = false
// AND LOCK IS PROPERLY UNSET
// ON NEXT ITERATION OF THE SPINLOCK WHILE, P1 WILL GET IT.