Решение 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++ компилируется в три отдельных шага:

  1. Считывание текущего значения из общей переменной s_curr в регистр процессора.
  2. Увеличение этого регистра на единицу.
  3. Запись значения регистра обратно в общую переменную s_curr.

Два потока, исполняющих эту последовательность, могут локально считать одно и то же значение из s_curr (скажем, 42), увеличить его на единицу (скажем, до 43) и опубликовать одно и то же итоговое значение. GetNext, тем самым, возвратит одно и то же число для обеих потоков, нарушая алгоритм. Хотя простой оператор s_curr++ и кажется атомарным, это совершенно не так.