2016-06-01 1 views
0

Ставки между друзьями. переменная суммы определяется как глобальная. , и у нас есть 2 потока, которые работают над циклом 1..100 и умножают сумму на 1 каждый цикл.оператор ++ (prefix) с темами

что будет напечатано? "sum ="?

int sum = 0; 

void func(){ 
    for (int i=0 ; i<= 100; i++){ 
     sum++; 
    } 
} 

int main(){ 

    t1 = Thread(func); 
    t2 = Thread(func); 

    t1.start(); 
    t2.start(); 

    t1.join(); 
    t2.join(); 

    cout << "sum = " << sum; 

    return 0; 

} 
+0

Все может случиться. Это UB-http :: //stackoverflow.com/questions/37325524/does-integer-overflow-cause-undefined-behavior-because-of-memory-corruption/37325854#37325854 –

+1

Из любопытства я исправил программу для C++ 11, и у меня было «202» в качестве первого результата. Это означает, что любой, кто считает диапазон 100-200, является неправильным. По какой-то причине внимательно посмотрите на код;) Однако он остается UB. – stefaanv

+0

Обратите внимание, что заголовок ссылается на ** префикс ** operator ++, но в коде используется ** postfix **. Не то, что это влияет на результат ... –

ответ

6

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

Вероятно, вы думаете о сумме ++ как о атомарной операции, но на самом деле это синтаксический сахар для sum = sum + 1. В этой операции существует вероятность состояния гонки, поэтому при каждом запуске сумма может быть различной.

Представьте, что текущее значение суммы равно 10. Затем t1 попадает в цикл и считывает значение суммы (10), а затем останавливается, чтобы начать запуск t2. Затем t2 будет считывать одно и то же значение (10) суммы как t1. Затем, когда каждый шаг увеличивается, они будут увеличивать его до 11. Если других условий гонки нет, конечное значение суммы будет 199.

Вот еще худший случай. Представьте, что текущее значение суммы равно 10. t1 входит в цикл и считывает значение sum (10), затем останавливается, чтобы t2 начал работать. t2, снова, считывает значение sum (10), а затем сам останавливается. Теперь t1 снова запускается, и он проходит через 10 раз, задавая значение sum на 20. Теперь t2 снова запускается и увеличивает сумму до 11, поэтому вы фактически уменьшили значение суммы.

+2

Хороший пример того, как можно уменьшить значение. – NathanOliver

+1

Было бы еще хуже, если основные аппаратные записи не являются атомарными или переменными не выровнены. Из-за ограниченного диапазона значений, о которых идет речь, это маловероятно, но представьте, когда один поток пытается записать 0x0100, второй 0x00FF, и вы закончите с наименьшим значащим из первого потока и наиболее значимым со второго. –

+0

Не уменьшается. Вы просто потеряли 10 приращений. – AhmadWabbi

8

Это неопределенное поведение, так что я гонг, чтобы сказать 42. Если у вас есть более чем один поток с доступом к общей переменной и по крайней мере из них является писателем, то вам нужно синхронизировать. Если у вас нет этой синхронизации, у вас есть неопределенное поведение, и мы не можем сказать вам, что произойдет.

Вы можете использовать std::mutex, или вы можете использовать std::atomic для получения синхронизации и определения поведения программ.

+0

Я понимаю, что для блокировки критического кода мне нужен мьютекс. но какой будет диапазон значений? –

+2

@ItayAveksis Theroreticlly это может быть что угодно, но что-то между 100-200 вероятнее всего. Проблема в том, что вы могли бы разорвать чтение в разделе инкремента, чтобы вы понятия не имели, какое фактическое значение является тем, что вы увеличиваете. – NathanOliver

+0

, но он все еще 100 <= sum <= 200 –

1

Поскольку приращение не atomic, это приведет к undefined behaviour.

+0

ОК. но какой будет диапазон значений? –

+0

Вы связались с французским cppreference.Я даже не знал, что есть французский cppreference – stefaanv

+1

@ItayAveksis: во-первых, «неопределенное поведение» означает, что вы не должны делать дополнительных предположений, во-вторых, мы не знаем, как реализуется «Thread», что тоже не помогает. – stefaanv

0

Это будет случайное значение между 100 и 200. Между двумя потоками существует условие гонки без взаимного исключения. Таким образом, некоторые операции с ++ будут потеряны. Вот почему вы получите 100, когда все операции потока нити будут потеряны, а 200, когда ничего не потеряно. Что-то между ними может случиться.

+0

Нет. Неопределенное поведение означает ** не определено **. Результат не ограничен между 100 и 200. (Обратите внимание, что серийная версия этой программы производит ** 202 **). –

+0

Это расстояние между 100 и 202 (не 200, так как i = 100 в цикле). «Undefined» означает, что вы не можете знать значение, но вы можете знать диапазон. В коде есть 202 операции инкремента. По крайней мере, 100 из них будут выполнены. В лучшем случае все они выполнены. Можете ли вы дать мне сценарий выполнения, который дает значение 99, например? @PeteBecker – AhmadWabbi

+0

В определении языка указано, что поведение этой программы ** не определено **. Это означает, что определение языка не говорит вам, что делает программа. (если результат должен был быть в определенном диапазоне, результат будет ** неуказан **). Вы можете предполагать все, что вам нравится, на том, что может произойти **, но если ваш компилятор не документирует, как он справляется с этим, ваши спекуляции ничего. Ничто не мешает компилятору наблюдать за тем, что несколько потоков записываются в одно и то же место без синхронизации, оптимизируя приращения и устанавливая значение 42. –