Существуют барьеры для чтения и записи; приобретать барьеры и снимать барьеры. И многое другое (io vs память и т. Д.).
Нет никаких барьеров для контроля «последней» ценности или «свежести» ценностей. Они нужны для управления относительным порядком доступа к памяти.
Барьеры записи управляют порядком записи. Поскольку запись в память выполняется медленно (по сравнению со скоростью ЦП), обычно существует очередь запросов на запись, где записи отправляются до того, как они «действительно произойдут». Хотя они ставятся в очередь по порядку, внутри очереди записи могут быть переупорядочены. (Так что, возможно, «очередь» - не лучшее название ...) Если вы не используете барьеры записи для предотвращения переупорядочения.
Барьеры чтения контролируют порядок чтения. Из-за спекулятивного выполнения (ЦП смотрит вперед и загружается из памяти раньше) и из-за наличия буфера записи (ЦП будет читать значение из буфера записи, а не из памяти, если она там есть, т. Е. ЦП думает, что он просто написал X = 5, тогда зачем читать его, просто посмотрите, что он все еще ожидает стать 5 в буфере записи) чтение может происходить не по порядку.
Это верно независимо от того, что компилятор пытается сделать в отношении порядка сгенерированного кода. т.е. 'volatile' в C ++ здесь не поможет, потому что он только сообщает компилятору, что нужно вывести код для повторного чтения значения из «памяти», он НЕ сообщает процессору, как / откуда его читать (например, из «памяти»). есть много вещей на уровне процессора).
Таким образом, барьеры чтения / записи создают блоки, чтобы предотвратить переупорядочение в очередях на чтение / запись (чтение обычно не занимает много места в очереди, но эффекты переупорядочения такие же).
Какие блоки? - приобретать и / или освобождать блоки.
Получение - например, чтение-получение (x) добавит чтение x в очередь чтения и очистит очередь (на самом деле не сбрасывать очередь, но добавить маркер, говорящий, что ничего не переупорядочивайте перед этим read, как если бы очередь была сброшена). Таким образом, более поздние (в порядке кода) чтения могут быть переупорядочены, но не до чтения x.
Освобождение - например, запись-освобождение (x, 5) сначала очистит (или отметит) очередь, а затем добавит запрос на запись в очередь на запись. Таким образом, более ранние записи не будут переупорядочены после x = 5, но обратите внимание, что более поздние записи могут быть переупорядочены до x = 5.
Обратите внимание, что я объединил чтение с получением и запись с выпуском, потому что это типично, но возможны разные комбинации.
Приобретение и Освобождение считаются «полубарьерами» или «полузаборами», потому что они только останавливают переупорядочивание в одном направлении.
Полный барьер (или полное ограждение) применяется как к получению, так и к освобождению, т. Е. Без переупорядочивания.
Обычно для безблокировочного программирования, C # или java «volatile» вам нужно / нужно читать-получать и писать-выпускать.
ie
void threadA()
{
foo->x = 10;
foo->y = 11;
foo->z = 12;
write_release(foo->ready, true);
bar = 13;
}
void threadB()
{
w = some_global;
ready = read_acquire(foo->ready);
if (ready)
{
q = w * foo->x * foo->y * foo->z;
}
else
calculate_pi();
}
Итак, во-первых, это плохой способ программирования потоков. Замки были бы безопаснее. Но просто чтобы проиллюстрировать препятствия ...
После того, как threadA () завершит запись foo, ему нужно написать foo-> ready LAST, действительно последний, иначе другие потоки могут увидеть foo-> ready раньше и получить неправильные значения x / y / z. Поэтому мы используем write_release
on foo-> ready, который, как упоминалось выше, эффективно «очищает» очередь записи (обеспечивая фиксацию x, y, z), а затем добавляет в очередь запрос ready = true. А затем добавляет запрос bar = 13. Обратите внимание, что, поскольку мы только что использовали барьер выпуска (не полный), bar = 13 может быть записан до готовности. Но нам все равно! т.е. мы предполагаем, что панель не изменяет общие данные.
Теперь threadB () должен знать, что когда мы говорим «готово», мы действительно имеем в виду готово. Итак, мы делаем read_acquire(foo->ready)
. Это чтение добавляется в очередь чтения, ЗАТЕМ очередь очищается. Обратите внимание, что w = some_global
также может быть в очереди. Итак, foo-> ready можно прочитать перед some_global
. Но, опять же, нас это не волнует, поскольку это не часть важных данных, к которым мы так осторожно относимся. Что нас действительно волнует, так это foo-> x / y / z. Таким образом, они добавляются в очередь чтения после сбора данных / маркера, гарантируя, что они будут прочитаны только после чтения foo-> ready.
Также обратите внимание, что обычно это те же самые барьеры, которые используются для блокировки и разблокировки мьютекса / CriticalSection / и т. Д. (т.е. получить при блокировке (), отпустить при разблокировке ()).
So,
Я почти уверен, что это (т.е. получение / выпуск) именно то, что говорится в документации MS, для чтения / записи «изменчивых» переменных в C # (и, возможно, для MS C ++, но это нестандартно). См. http://msdn.microsoft.com/en-us/library/aa645755(VS.71).aspx, включая «Непостоянное чтение имеет« семантику получения »; то есть гарантированно произойдет до любых ссылок на память, которые происходят после него ...»
Я думаю, что java - это то же самое, хотя я не так хорошо знаком. Я подозреваю, что это точно так же, потому что обычно вам не нужно больше гарантий, чем чтение-получение / запись-выпуск.
В своем вопросе вы были на правильном пути, думая, что все дело в относительном порядке - у вас просто был порядок в обратном порядке (т.е. «считываемые значения, по крайней мере, так же актуальны, как и считанные до барьера? "- нет, чтения перед барьером неважны, его чтения ПОСЛЕ барьера, которые гарантированно будут ПОСЛЕ, и наоборот для записи).
И обратите внимание, как уже упоминалось, переупорядочивание происходит как при чтении, так и при записи, поэтому использование барьера только в одном потоке, а не в другом, НЕ РАБОТАЕТ. т.е. без чтения-получения недостаточно одной записи-выпуска. т.е. даже если вы напишете его в правильном порядке, он может быть прочитан в неправильном порядке, если вы не использовали барьеры чтения вместе с барьерами записи.
И, наконец, обратите внимание, что программирование без блокировок и архитектуры памяти ЦП могут быть на самом деле намного сложнее, чем это, но придерживаясь приобретения / выпуска, вы довольно далеко уйдете.
person
tony
schedule
24.11.2009