(Оптимизация?) Ошибка, связанная с GCC std::thread

При тестировании некоторых функций с std::thread друг столкнулся с проблемой с GCC, и мы подумали, что стоит спросить, является ли это ошибкой GCC или, возможно, что-то не так с этим кодом (код печатает (например) "7 8 9 10 1 2 3 ", но мы ожидаем, что каждое целое число в [1,10] будет напечатано):

#include <algorithm>
#include <iostream>
#include <iterator>
#include <thread>

int main() {
    int arr[10];
    std::iota(std::begin(arr), std::end(arr), 1);
    using itr_t = decltype(std::begin(arr));

    // the function that will display each element
    auto f = [] (itr_t first, itr_t last) {
        while (first != last) std::cout<<*(first++)<<' ';};

    // we have 3 threads so we need to figure out the ranges for each thread to show
    int increment = std::distance(std::begin(arr), std::end(arr)) / 3;
    auto first    = std::begin(arr);
    auto to       = first + increment;
    auto last     = std::end(arr);
    std::thread threads[3] = {
        std::thread{f, first, to},
        std::thread{f, (first = to), (to += increment)},
        std::thread{f, (first = to), last} // go to last here to account for odd array sizes
    };
    for (auto&& t : threads) t.join();
}

Работает следующий альтернативный код:

int main()
{
    std::array<int, 10> a;
    std::iota(a.begin(), a.end(), 1);
    using iter_t = std::array<int, 10>::iterator;
    auto dist = std::distance( a.begin(), a.end() )/3;
    auto first = a.begin(), to = first + dist, last = a.end();
    std::function<void(iter_t, iter_t)> f =
        []( iter_t first, iter_t last ) {
            while ( first != last ) { std::cout << *(first++) << ' '; }
        };
    std::thread threads[] {
            std::thread { f,  first, to },
            std::thread { f, to, to + dist },
            std::thread { f, to + dist, last }
    };
    std::for_each(
        std::begin(threads),std::end(threads),
        std::mem_fn(&std::thread::join));
    return 0;
}

Мы подумали, что, возможно, это как-то связано с непоследовательной оценкой арности функции или просто с тем, как std::thread должен работать при копировании не-std::ref аргументов. Затем мы протестировали первый код с помощью Clang, и он работает (поэтому мы начали подозревать ошибку GCC).

Используемый компилятор: GCC 4.7, Clang 3.2.1

EDIT: Код GCC дает неверный результат в первой версии кода, но во второй версии он дает правильный результат.


person iamOgunyinka    schedule 04.08.2014    source источник
comment
Я не понимаю проблемы. Какой код проблематичен, тот, что в pastebin, или тот, что размещен здесь? В любом случае, пожалуйста, разместите здесь тот, который сейчас находится на pastebin.   -  person filmor    schedule 04.08.2014
comment
Извините, я не указал хорошо. Код pastebin - это проблема. Я использую свой мобильный телефон, и мне трудно печатать с него   -  person iamOgunyinka    schedule 04.08.2014
comment
И какой результат вы получили, и что вы ожидали?   -  person Useless    schedule 04.08.2014
comment
Вывод кода pastebin-> 7 8 9 10 1 2 3, поэтому 4 5 6 пропало   -  person iamOgunyinka    schedule 04.08.2014
comment
Вы пытались передать аргументы конструктору потока так же, как вы делаете это в опубликованном коде? Возможно, запятая внутри конструктора массива не является точкой последовательности, поэтому может случиться так, что и во втором, и в третьем потоке first указывает на to + dist.   -  person filmor    schedule 04.08.2014
comment
Я пробовал это, это на самом деле проблема, я напишу ответ на это.   -  person filmor    schedule 04.08.2014
comment
@filmor В С++ 11 нет точек следования. Однако порядок оценки внутри списка инициализации в фигурных скобках полностью определен, как если бы в каждом из (верхнего уровня) , была точка следования.   -  person dyp    schedule 04.08.2014
comment
Возможно, эта ошибка, из-за которой этот вопрос будет грубой копией этот   -  person dyp    schedule 04.08.2014
comment
@dyp верен - это ошибка в gcc, она исправлена ​​в последней версии.   -  person Jonathan Wakely    schedule 04.08.2014
comment
Я также подозреваю, что std::cout не блокируется при использовании в f. Когда у вас есть несколько потоков, использующих его для вывода параллельно, вы можете получить странные результаты.   -  person jotik    schedule 13.10.2014
comment
Он выводит 1 2 3 4 5 6 7 8 9 10, как и ожидалось, при компиляции с gcc-4.9.2.   -  person Maxim Egorushkin    schedule 10.11.2014


Ответы (1)


Из этой модифицированной программы:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <thread>
#include <sstream>

int main()
{
    int arr[10];
    std::iota(std::begin(arr), std::end(arr), 1);
    using itr_t = decltype(std::begin(arr));

    // the function that will display each element
    auto f = [] (itr_t first, itr_t last) {
        std::stringstream ss;
        ss << "**Pointer:" << first  << " | " << last << std::endl;
        std::cout << ss.str();
        while (first != last) std::cout<<*(first++)<<' ';};

    // we have 3 threads so we need to figure out the ranges for each thread to show
    int increment = std::distance(std::begin(arr), std::end(arr)) / 3;
    auto first    = std::begin(arr);
    auto to       = first + increment;
    auto last     = std::end(arr);
    std::thread threads[3] = {
        std::thread{f, first, to},
#ifndef FIX
        std::thread{f, (first = to), (to += increment)},
        std::thread{f, (first = to), last} // go to last here to account for odd array sizes
#else
        std::thread{f,  to,  to+increment},
        std::thread{f,  to+increment, last} // go to last here to account for odd array sizes
#endif
    };
    for (auto&& t : threads) {
        t.join();
    }
}

Я добавляю отпечатки указателя first и last для лямбда-функции f и нахожу следующие интересные результаты (когда FIX не определено):

**Pointer:0x28abd8 | 0x28abe4
1 2 3 **Pointer:0x28abf0 | 0x28abf0
**Pointer:0x28abf0 | 0x28ac00
7 8 9 10

Затем я добавляю код для случая #ELSE для случая #ifndef FIX. Это работает хорошо.

- Обновление: этот вывод, исходный пост ниже, неверен. Моя вина. См. комментарий Джоша ниже -

Я считаю, что 2-я строка std::thread{f, (first = to), (to += increment)}, threads[] содержит ошибку: присваивание внутри двух пар скобок может быть оценено синтаксическим анализатором в любом порядке. Тем не менее порядок присваивания 1-го, 2-го и 3-го аргумента конструктора должен сохранять заданный порядок.

--- Обновление: исправлено ---

Таким образом, приведенные выше результаты печати отладки предполагают, что GCC4.8.2 (моя версия) все еще содержит ошибки (не говоря уже о GCC4.7), но GCC 4.9.2 исправляет эту ошибку, как сообщил Максим Егорушкин (см. комментарий выше).

person Robin Hsu    schedule 10.11.2014
comment
Точно так же для выражения (a+b)*(c+d) две пары скобок могут выполняться в любом порядке, который предпочитает компилятор. - person Robin Hsu; 10.11.2014
comment
Робин Хсу, в соответствии со стандартом C++ и CppReference говорится, что In list-initialization, every value computation and side effect of a given initializer clause is sequenced before every value computation and side effect associated with any initializer clause that follows it in the comma-separated list of the initializer list. Итак, , говоря, что The compiler can choose which parenthesis to evaluate first неверно. - person iamOgunyinka; 13.11.2014
comment
Джош, ты прав. Я исправил это в своем ответе. Жалко всех. - person Robin Hsu; 14.11.2014