Синхронизация потоков. Как именно блокировка делает доступ к памяти «правильным»?

Прежде всего, я знаю, что lock {} - это синтетический сахар для Класс монитора . (о, синтаксический сахар)

Я играл с простыми проблемами многопоточности и обнаружил, что не могу полностью понять, как блокировка некоторого произвольного СЛОВА памяти защищает от кэширования целую другую память - регистры / кеш процессора и т. д. проще использовать примеры кода, чтобы объяснить то, о чем я говорю:

for (int i = 0; i < 100 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}

В конце концов ms_Sum будет содержать 100000000 , что, конечно, ожидается.

Теперь мы стареем. собираюсь выполнить тот же цикл, но на 2 разных потоках и с уменьшенным вдвое верхним пределом.

for (int i = 0; i < 50 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}

Из-за отсутствия синхронизации мы получаем неверный результат - на моей 4-ядерной машине это почти случайное число 52 388 219 , которое является чуть больше половины от 100 000 000 . Если мы заключим ms_Sum + = 1; в lock {} , мы, естественно, получим абсолютно правильный результат 100 000 000 . Но что меня интересует (честно говоря, я был ожидал подобного поведения), добавление блокировки до или после ms_Sum + = 1; строка дает ответ почти правильно:

for (int i = 0; i < 50 * 1000 * 1000; ++i) {
    lock (ms_Lock) {}; // Note curly brackets

    ms_Sum += 1;
}

В этом случае я обычно получаю ms_Sum = 99 999 920 , что очень близко.

Вопрос: почему именно ​​блокировка (ms_Lock) {ms_Counter + = 1; } делает программу полностью правильной, но lock (ms_Lock) {}; ms_Counter + = 1; только почти правильно; как блокировка произвольной переменной ms_Lock делает всю память стабильной?

Большое спасибо!

P.S. Ушел читать книги о многопоточности.

ПОДОБНЫЙ ВОПРОС (S)

Как оператор блокировки обеспечивает внутрипроцессорную синхронизацию?

Синхронизация потоков. Почему именно этой блокировки недостаточно для синхронизации потоков

14
задан Community 23 May 2017 в 11:53
поделиться