среда, 31 августа 2011 г.

Пример работы InterlockedExchange

long g_sum;

unsigned __stdcall ThreadFunc(void* argList){
  for(int i = 0; i<100000000; i++){
    g_sum++;
  }
  return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
  /* Запускаем 2 потока с одной и той же функцией */
  HANDLE hThreads[2];
  hThreads[0] = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, NULL, 0, NULL);
  hThreads[1] = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, NULL, 0, NULL);

  /* Ждем... */
  WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);

  _tprintf(TEXT("sum: %d\n"), g_sum);

  CloseHandle(hThreads[0]);
  CloseHandle(hThreads[1]);
  return 0;
}

Скорее всего, результат будет всегда разный, между 100 и 200 миллионами (можно увеличить число операций). Если бы каждый поток блокировал g_sum от другого потока на время изменения, то мы бы получили ровно 200 миллионов.

Самое простое, что можно сделать для этого - заменить g_sum++ на InterlockedExchangeAdd(&g_sum, 1). Теперь результат всегда корректен. Блокировка происходит на уровне процессора.

Почему обычный инкремент работает некорректно?

g_sum++ эквивалентно:
  mov eax, g_sum;
  add eax, 1;
  mov g_sum, eax;

То есть, реально это несколько операций. И поток может быть прерван другим потоком после выполнения любой из них. Получается какая-то каша.