Решение 11 распространенных проблем в многопоточном коде - Состязание за данные
ОГЛАВЛЕНИЕ
Состязание за данные
(оно же условие состязания) возникает при одновременном доступе к данным из нескольких потоков. Говоря конкретнее, это случается, когда один или несколько потоков записывают кусок данных, в то время как один или несколько потоков считывают этот самый кусок данных. Эта проблема возникает потому, что программы Windows (как в C++, так и в Microsoft .NET Framework) построены на основе концепции общей памяти, где все потоки в процессе могут получать доступ к данным, находящимся в одном и том же виртуальном пространстве адресов. Для общего доступа можно использовать статические переменные и выделение памяти из кучи.
Рассмотрим следующий канонический пример:
static class Counter {
internal static int s_curr = 0;
internal static int GetNext() {
return s_curr++;
}
}
Целью класса Counter («Счетчик») предположительно является выдача уникального номера каждому вызову к методу GetNext. Если два потока в программе одновременно вызывают GetNext, двум потокам может быть дан одинаковый номер. Причина этого состоит в том, что s_curr++ компилируется в три отдельных шага:
- Считывание текущего значения из общей переменной s_curr в регистр процессора.
- Увеличение этого регистра на единицу.
- Запись значения регистра обратно в общую переменную s_curr.
Два потока, исполняющих эту последовательность, могут локально считать одно и то же значение из s_curr (скажем, 42), увеличить его на единицу (скажем, до 43) и опубликовать одно и то же итоговое значение. GetNext, тем самым, возвратит одно и то же число для обеих потоков, нарушая алгоритм. Хотя простой оператор s_curr++ и кажется атомарным, это совершенно не так.