Сначала давайте подведем итог, что же на самом деле является фундаментальной проблемой многопоточности - два потока пытаются получить доступ к одному и тому же фрагменту памяти одновременно.Вы можете себе представить, что когда это происходит, мы не можем гарантировать, что часть памяти находится в допустимом состоянии, а наша программа может быть неправильной.
Попытка сохранить этот очень высокий уровень, часть работы процессоровэто бросает прерывания, которые в основном говорят потоку прекратить то, что делает, и сделать что-то еще.Именно в этом и заключается большая часть проблемы многопоточности.Поток может быть прерван в середине задачи.Представьте, что один поток прерывается в середине операции, и существует некоторое промежуточное значение мусора, потому что поток не завершил свою задачу.Другой поток может прийти и прочитать это значение и разрушить правильность вашей программы.
ОС достигает этого с помощью атомарных инструкций.Не вдаваясь в подробности, представьте, что есть некоторые инструкции, которые гарантированно либо будут выполнены, либо не завершены.Это означает, что если поток проверяет результат инструкции, он не видит промежуточных результатов.Таким образом, атомарный метод добавления будет показывать значение до добавления или после добавления, но не во время добавления, когда они могут находиться в некотором промежуточном состоянии.
Теперь, если у вас есть несколько атомарных инструкций, вы можете себе представить, что вы можете создавать абстракции более высокого уровня, связанные с потоками и безопасностью потоков, на их обратной стороне.Возможно, самый простой пример блокировки, созданной с помощью теста и установленного примитива.Взгляните на эту статью в Википедии https://en.wikipedia.org/wiki/Test-and-set. Теперь это было, вероятно, много, потому что эти вещи становятся довольно сложными.Но я попытаюсь привести пример, который проясняет.Если у вас есть два запущенных процесса, которые пытаются получить доступ к некоторой части кода, очень наивным решением было бы создать переменную блокировки
boolean isLocked = false;
Каждый раз, когда процесс пытался получить эту блокировку, вы можете просто проверить isLocked == false и дождитесь isLocked == true, прежде чем выполнять какой-либо код.Например ...
while(isLocked){
//wait for isLocked == false
}
isLocked = true;
// execute the code you want to be locked
isLocked = false;
Конечно, мы знаем, что что-то такое простое, как установка или чтение логического значения, может быть прервано и вызвать хаос в многопоточности.Итак, хорошие люди, которые разработали ядра, процессоры и оборудование, создали атомарный тест и установили операцию, которая возвращает старое значение логического значения и устанавливает новое значение в значение true.Поэтому, конечно, вы можете реализовать нашу блокировку выше, выполнив что-то вроде.
while(testAndSet(isLocked)){ //wait until the old value returned is
false so the lock is unlocked } //do some critical stuff
//unlock after you did the critical stuff lock = false;
Я только показываю реализацию базовой блокировки выше, чтобы доказать, что возможно построить абстракции более высокого уровня на атомарных инструкциях.На мой взгляд, атомарный инструктаж находится на настолько низком уровне, насколько вы можете получить его концептуально, не вдаваясь в аппаратные особенности.Вы можете себе представить, что в аппаратном обеспечении аппаратное обеспечение должно каким-то образом устанавливать какой-либо флаг, когда читается память, что препятствует доступу другого потока к той же памяти.
Надеюсь, это поможет!