Прежде всего, я знаю, что lock{}
- синтетический сахар для класса Monitor
. (oh, синтаксический)
Я играл с несколькими задачами многопоточности и обнаружил, что не может полностью понять, как блокировка какого-либо произвольного WORD памяти защищает целую другую память от кэширования - это регистры/кеш процессора и т.д. Легче использовать примеры кода для объяснения того, что я говорю о:
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
. Но то, что интересно для меня (по-настоящему говоря, что ожидалось подобное поведение), добавляет lock
до после 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
, что очень близко.
Вопрос: почему именно lock(ms_Lock) { ms_Counter += 1; }
делает программу полностью корректной, но lock(ms_Lock) {}; ms_Counter += 1;
только почти корректна; как блокировка произвольной переменной ms_Lock
делает целую память стабильной?
Спасибо большое!
P.S. Ушел читать книги о многопоточности.
ПОДОБНЫЙ ВОПРОС (S)
Как оператор блокировки обеспечивает синхронизацию внутри процессора?
Синхронизация потоков. Почему именно этой блокировки недостаточно для синхронизации потоков.