Что касается вашей первой точки зрения, когда процессор записывает некоторые данные в память, эти данные всегда записываются правильно и не могут быть "перехвачены" другими записями процессами потоков, ОС и т. Д. Это не вопрос синхронизации, просто требуетсядля обеспечения правильного поведения оборудования.
Синхронизация - это программная концепция, требующая аппаратной поддержки.Предположим, что вы просто хотите получить замок.Предполагается, что он свободен, когда в 0 и заблокирован, когда в 1.
Основной способ сделать это -
got_the_lock=0
while(!got_the_lock)
fetch lock value from memory
set lock value in memory to 1
got_the_lock = (fetched value from memory == 0)
done
print "I got the lock!!"
Проблема в том, что если другие потоки делают то же самое наВ то же время и считывание значения блокировки до того, как оно будет установлено в 1, несколько потоков могут подумать, что получили блокировку.
Чтобы избежать этого, нужен атомарный доступ к памяти.Атомарный доступ, как правило, представляет собой цикл чтения-изменения-записи данных в памяти, который не может быть прерван и который запрещает доступ к этой информации до завершения.Таким образом, не все обращения являются атомарными, только специальная операция чтения-изменения-записи, и она реализуется благодаря специальной поддержке процессора (см. Инструкции test-and-set или fetch-and-add , например).Большинству обращений это не нужно и может быть обычным.Атомарный доступ в основном используется для синхронизации потоков, чтобы гарантировать, что только один поток находится в критической секции.
Так почему атомный доступ дорог?Есть несколько причин.
- Во-первых, необходимо обеспечить правильный порядок инструкций.Вы, вероятно, знаете, что порядок команд может отличаться от порядка программ инструкций при условии соблюдения семантики программы.Это в значительной степени используется для повышения производительности: команды переупорядочения компилятора, процессор выполняют их не по порядку, кэши обратной записи записывают данные в память в любом порядке, а буфер записи в память делает то же самое.Это переупорядочение может привести к неправильному поведению.
1 while (x--) ; // random and silly loop
2 f(y);
3 while(test_and_set(important_lock)) ; //spinlock to get a lock
4 g(z);
Очевидно, что инструкция 1 не является ограничивающей, а 2 может быть выполнена раньше (и, вероятно, 1 будет удалено оптимизирующим компилятором).Но если 4 выполняется до 3, поведение будет не таким, как ожидалось.
Чтобы избежать этого, атомарный доступ очищает буфер инструкций и памяти, который требует десятков циклов (см. барьер памяти ).
Без конвейера вы оплачиваете полную задержку операции: считываете данные из памяти, изменяете их и записываете обратно.Эта задержка всегда происходит, но для регулярного доступа к памяти вы можете выполнять другую работу в течение этого времени, которая в значительной степени скрывает задержку.
Атомный доступ требует по крайней мере 100-200 циклов на современных процессорах и соответственно очень дорого.
Как это сочетается?Почему блокировка для изменения переменной незаметна быстро, а блокировка для чего-то еще такого дорогого?Или это одинаково дорого, и должен быть большой предупреждающий знак при использовании - скажем, long и double, потому что они всегда неявно требуют синхронизации?
Обычный доступ к памяти не является атомарным.Только конкретные инструкции по синхронизации стоят дорого.