Потоки С++: общая память не обновляется, несмотря на отсутствие гонки

Я экспериментирую со стандартными потоками С++. Я написал небольшой тест для проверки производительности и общей пропускной способности. Принцип заключается в том, чтобы запустить в один или несколько потоков цикл из 1 миллиарда итераций, время от времени делая небольшие паузы.

В первой версии я использовал счетчики в разделяемой памяти (то есть обычные переменные). Я ожидал следующего результата:

Sequential      1e+009 loops    4703 ms 212630 loops/ms
2 thrds:t1      1e+009 loops    4734 ms 211238 loops/ms
2 thrds:t2      1e+009 loops    4734 ms 211238 loops/ms
2 thrds:tt      2e+009 loops    4734 ms 422476 loops/ms
manythrd tn     1e+009 loops    7094 ms 140964 loops/ms
...  
manythrd tt     6e+009 loops    7094 ms 845785 loops/ms

К сожалению, дисплей показал некоторые счетчики, как если бы они были неинициализированы!

Я мог бы решить эту проблему, сохранив конечное значение каждого счетчика в atomic<> для последующего отображения. Однако я не понимаю, почему версия, основанная на простой разделяемой памяти, не работает должным образом: каждый поток использует свой собственный счетчик, так что условия гонок нет. Даже поток отображения обращается к счетчикам только после завершения счетных потоков. Использование volatile тоже не помогло.

Может ли кто-нибудь объяснить мне это странное поведение (как будто память не обновлялась) и сказать, не пропустил ли я что-то?

Здесь общие переменные:

const int maxthread = 6;
atomic<bool> other_finished = false;
atomic<long> acounter[maxthread];

Вот код многопоточной функции:

void foo(long& count, int ic, long maxcount)   
{
    count = 0;  
    while (count < maxcount) {
        count++;
        if (count % 10000000 == 0)
            this_thread::sleep_for(chrono::microseconds(1));
    }
    other_finished = true;      // atomic: announce work is finished
    acounter[ic] = count;       // atomic: share result 
}

Вот пример того, как я называю бенчмаркинг потоков:

mytimer.on();                 // second run, two threadeds
thread t1(foo, counter[0], 0, maxcount);  // additional thread
foo(counter[1], 1, maxcount);         // main thread
t1.join();                    // wait end of additional thread
perf = mytimer.off();     
display_perf("2 thrds:t1", counter[0], perf);  // non atomic version of code
display_perf("2 thrds:t2", counter[1], perf);
display_perf("2 thrds:tt", counter[0] + counter[1], perf);

person Christophe    schedule 26.06.2014    source источник
comment
Да ! Извините: MSVC 2013 на Win 8.1 с Intel i7   -  person Christophe    schedule 27.06.2014
comment
Скорее всего не связано с проблемой. Однако, что касается производительности, обратите внимание на ложный обмен, т. е. разные потоки не должны писать в переменные, находящиеся в одной строке кеша, в вашем случае counter.   -  person nosid    schedule 27.06.2014
comment
Очень интересная статья о ложном обмене. Я подозревал что-то с кешем. Однако после вашего решения с помощью std::ref() я создал вариант своей программы, используя глобальный массив и без передачи ссылок. Это сработало нормально, что подтвердило, что проблема была не в кеше, а в ссылке.   -  person Christophe    schedule 27.06.2014


Ответы (1)


Вот упрощенная версия для воспроизведения проблемы:

void deep_thought(int& value) { value = 6 * 9; }

int main()
{
    int answer = 42;
    std::thread{deep_thought, answer).join();
    return answer; // 42
}

Это похоже на передачу ссылки на answer в рабочую функцию и назначение 6 * 9 на ссылку и, следовательно, на answer. Однако конструктор std::thread делает копию answer и передает ссылку на копию рабочей функции, а переменная answer в основном потоке никогда не изменяется.

И GCC-4.9, и Clang-3.5 отвергают приведенный выше код, потому что рабочую функцию нельзя вызвать с помощью ссылки lvalue. Вы можете решить проблему, передав переменную с std::ref:

    std::thread{deep_thought, std::ref(answer)}.join();
person nosid    schedule 26.06.2014
comment
Возможно, стоит упомянуть, что решение заключается в использовании файла std::reference_wrapper. - person T.C.; 27.06.2014
comment
@T.C.: Спасибо за подсказку. Я обновил ответ. - person nosid; 27.06.2014