На уровне ЦП, да, каждый процессор в конечном итоге увидит изменение адреса памяти. Даже без замков и барьеров памяти. Блокировки и барьеры просто гарантировали бы, что все это происходило в относительном порядке (с другими инструкциями), так что это казалось правильным для вашей программы.
Проблема не в кэш-когерентности (я надеюсь, что книга Джо Даффи не допустит этой ошибки). Кэши остаются согласованными - просто это занимает время, и процессоры не удосуживаются ждать, пока это произойдет, - если вы не обеспечите это. Таким образом, вместо этого процессор переходит к следующей инструкции, которая может или не может произойти до предыдущей (поскольку каждая операция чтения / записи памяти занимает разное количество времени. По иронии судьбы , потому что времени для процессоров, чтобы договориться о когерентности и т. Д. - это приводит к тому, что некоторые линии кэширования становятся согласованными быстрее, чем другие (т.е. в зависимости от того, была ли строка модифицирована, исключена, общая или недействительна, требуется больше или меньше работы попасть в нужное состояние).)
Таким образом, чтение может показаться старым или из устаревшего кэша, но на самом деле это произошло раньше, чем ожидалось (обычно из-за предварительного просмотра и предсказания перехода). Когда действительно было прочитано , кэш был согласованным, с тех пор он просто изменился. Таким образом, значение не было старым, когда вы читали его, но теперь, когда вам это нужно. Вы только что прочитали это слишком рано. : - (
Или, эквивалентно, он был написан позже, чем логика вашего кода думала, что он будет написан.
Или оба.
В любом случае, если бы это был C / C ++, даже без блокировок / барьеров, вы бы в итоге получили бы обновленные значения. (обычно в течение нескольких сотен циклов, так как память занимает столько же времени). В C / C ++ вы можете использовать volatile (слабое непотоковое volatile), чтобы гарантировать, что значение не будет считано из регистра. (Теперь есть некогерентный кеш! Т.е. регистры)
В C # я недостаточно знаю о CLR, чтобы знать, как долго значение может оставаться в регистре, или как гарантировать, что вы действительно перечитываете из памяти. Вы потеряли «слабую» волатильность.
Я подозреваю, что до тех пор, пока доступ к переменным не будет полностью скомпилирован, у вас в конечном итоге закончатся регистры (у x86 не так много для начала), и вы перечитаете.
Но никаких гарантий, которые я вижу. Если бы вы могли ограничить ваше volatile-чтение определенной точкой в вашем коде, которая была частой, но не слишком частой (то есть начало следующей задачи в цикле while (things_to_do)), тогда это могло бы быть лучшим, что вы можете сделать.