Распараллелить цикл, используя std::thread и передовой опыт

Возможный дубликат:
C++ 2011: std::thread: простой пример распараллеливания цикла?

Рассмотрим следующую программу, распределяющую вычисления по элементам вектора (раньше я никогда не использовал std::thread):

// vectorop.cpp
// compilation: g++ -O3 -std=c++0x vectorop.cpp -o vectorop -lpthread
// execution: time ./vectorop 100 50000000 
// (100: number of threads, 50000000: vector size)
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <thread>
#include <cmath>
#include <algorithm>
#include <numeric>

// Some calculation that takes some time
template<typename T> 
void f(std::vector<T>& v, unsigned int first, unsigned int last) {
    for (unsigned int i = first; i < last; ++i) {
        v[i] = std::sin(v[i])+std::exp(std::cos(v[i]))/std::exp(std::sin(v[i])); 
    }
}

// Main
int main(int argc, char* argv[]) {

    // Variables
    const int nthreads = (argc > 1) ? std::atol(argv[1]) : (1);
    const int n = (argc > 2) ? std::atol(argv[2]) : (100000000);
    double x = 0;
    std::vector<std::thread> t;
    std::vector<double> v(n);

    // Initialization
    std::iota(v.begin(), v.end(), 0);

    // Start threads
    for (unsigned int i = 0; i < n; i += std::max(1, n/nthreads)) {
        // question 1: 
        // how to compute the first/last indexes attributed to each thread 
        // with a more "elegant" formula ?
        std::cout<<i<<" "<<std::min(i+std::max(1, n/nthreads), v.size())<<std::endl;
        t.push_back(std::thread(f<double>, std::ref(v), i, std::min(i+std::max(1, n/nthreads), v.size())));
    }

    // Finish threads
    for (unsigned int i = 0; i < t.size(); ++i) {
        t[i].join();
    }
    // question 2: 
    // how to be sure that all threads are finished here ?
    // how to "wait" for the end of all threads ?

    // Finalization
    for (unsigned int i = 0; i < n; ++i) {
        x += v[i];
    }
    std::cout<<std::setprecision(15)<<x<<std::endl;
    return 0;
}

В код уже встроено два вопроса.

Третий вопрос: этот код полностью исправен или его можно написать более элегантно, используя std::threads? Я не знаю "хорошей практики" использования std::thread...


person Vincent    schedule 26.12.2012    source источник
comment
На вопрос 2 отвечает комментарий, непосредственно предшествующий ему.   -  person Seth Carnegie    schedule 26.12.2012
comment
Также для вопроса 2 вы можете использовать барьер.   -  person Adri C.S.    schedule 26.12.2012
comment
комментарий от меня, поэтому я не знаю, завершает ли цикл соединения все потоки перед переходом к следующей инструкции.   -  person Vincent    schedule 26.12.2012
comment
Для элегантности вы, вероятно, захотите использовать std::future вместо того, чтобы вообще использовать потоки напрямую.   -  person Jerry Coffin    schedule 26.12.2012
comment
@JerryCoffin: Не могли бы вы привести пример кода, выполняющего то же самое самым элегантным способом, который вы имеете в виду?   -  person Vincent    schedule 26.12.2012
comment
Вот, как вам удобно: parlab.eecs.berkeley.edu/wiki/_media /patterns/paraplop_g1_3.pdf   -  person Adri C.S.    schedule 26.12.2012
comment
См. мой ответ здесь: stackoverflow.com/a/10796261/893693   -  person Stephan Dollberg    schedule 27.12.2012


Ответы (1)


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

const size_t itemsPerThread = std::max(1, n/threads);
for (size_t nextIndex= 0; nextIndex< v.size(); nextIndex+= itemsPerThread)
{
    const size_t beginIndex = nextIndex;
    const size_t endIndex =std::min(nextIndex+itemsPerThread, v.size())
    std::cout << beginIndex << " " << endIndex << std::endl;
    t.push_back(std::thread([&v,beginIndex ,endItem]{f(v,beginIndex,endIndex);});
}

Расширенный вариант использования предполагает использование пула потоков, но то, как это будет выглядеть, зависит от дизайна вашего приложения и не рассматривается в STL. Хороший пример модели потоков см. в Qt Framework. Если вы только начинаете работать с потоками, сохраните это на потом.

На второй вопрос уже был дан ответ в комментариях. Функция std::thread::join будет ждать (блокироваться), пока поток не завершится. Вызывая функцию соединения в каждом потоке и достигая кода после функции соединения, вы можете быть уверены, что все потоки завершены и теперь могут быть удалены.

person Peter    schedule 26.12.2012
comment
std::iota и конструктор заполнения делают очень разные вещи. они не могут быть взаимозаменяемы. - person Kyle Lutz; 26.12.2012
comment
Пропустить чтение iota как itoa. удалил часть, относящуюся к его использованию. - person Peter; 26.12.2012