Параллельное приращение переменной int - программирование

Параллельное приращение переменной int

Вопрос о собеседовании

int count = 0;

void func1()
{
  for ( int i =0 ; i < 10; ++i )
    count = count + 1;
}

void func2()
{
  for ( int i =0 ; i < 10; ++i )
    count++;
}

void func3()
{
  for ( int i =0 ; i < 10; ++i )
    ++count;
}

int main()
{
  thread(func1);
  thread(func2);
  thread(func3);

  //joining all the threads

  return 0;
}

Возникает вопрос: какой теоретический эффект может иметь диапазон значений count? Верхняя граница, по-видимому, равна 30, но какая нижняя? Они сказали мне это 10, но я не уверен в этом. В противном случае, зачем нам нужны барьеры памяти?

Итак, какова нижняя граница диапазона?

4b9b3361

Ответ 1

Ответ Джеймса Канзе является правильным для всех практических целей, но в этом конкретном случае, если код точно так же написан и thread, который здесь используется std::thread из С++ 11, поведение фактически определено.

В частности, thread(func1); запустит поток с func1. Затем, в конце выражения, объект временной нити будет уничтожен, без соединения или отсоединения. Таким образом, поток все еще соединяется, и стандарт определяет, что в таком случае деструктор вызывает std::terminate. (См. [Thread.thread.destr]: "Если joinable(), то завершение(), иначе никаких эффектов." ) Таким образом, ваша программа прерывается.

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

Ответ 2

Это поведение undefined, поэтому count может принимать любое значение можно себе представить. Или программа может потерпеть крах.

Ответ 3

Начиная с простой части, очевидная верхняя граница равна 30, поскольку, если все идет правильно, у вас есть 3 вызова функций; каждый из которых способен увеличивать count 10 раз. В целом: 3 * 10 = 30.

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

count_temp = count;
count_temp = count_temp+1;
count = count_temp;

Очевидно, что если все они выполняют один и тот же код одновременно, у вас есть только 10 реальных приращений, так как все они читают одно и то же начальное значение count и все записывают одно и то же добавленное значение.

Ответ 4

Прежде всего, я хотел бы поблагодарить вас, ребята, за то, что я объяснил, что я подробно читаю стандарт. В противном случае я не смог бы продолжить эту дискуссию.

Стандартные состояния довольно четко указаны в разделе 1.10, пункт 21: The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

Однако термин undefined behavior также определен в стандарте, раздел 1.3.24: behavior for which this International Standard imposes no requirements... Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment...

Принимая во внимание ответ Sebasian относительно std::terminate и работая в предположении, что эти потоки не будут генерировать исключение, тем самым вызывая преждевременное прекращение; в то время как стандарт не определяет результат - довольно очевидно, что это может быть из-за простоты алгоритма. Другими словами, хотя 100% точный ответ будет заключаться в том, что результат undefined - я все же утверждаю, что диапазон возможных результатов хорошо определен и составляет 10-30 из-за characteristic of the environment.

BTW - я действительно хотел сделать этот комментарий вместо другого ответа, однако он был слишком длинным