使用tbb :: paralllel_for

时间:2019-07-03 17:56:33

标签: c++ multithreading parallel-processing tbb

我正在尝试使用Intel的TBB对一维数组A[]进行计算。问题在于,默认情况下,像tbb::parallel_for这样的算法会递归地将数组切成两半,将每个块发送到任务池以供线程窃取。

但是,我希望所有线程以线性方式“扫描”阵列。例如,使用4个线程使它们并行计算任何顺序的前A[0], A[1], A[2]A[3]。然后,以任意顺序计算集合A[4], A[5], A[6]A[7]

现在,parallel_for在经过两次递归拆分后将分别计算第一个A[0], A[2], A[4]A[6]。然后,A[1], A[3], A[5]A[7](或类似的东西)。

我正在使用C ++ 14和Intel的Threading Building Blocks。关于迭代空间的划分,parallel_reduceparallel_scan之类的算法以类似的方式运行,因此它们并没有太大帮助。

我的猜测是我确实定义了自己的迭代空间对象,但是我不知道具体如何。 docs给出了以下定义:

class R {
    // True if range is empty
    bool empty() const;
    // True if range can be split into non-empty subranges
    bool is_divisible() const;
    // Splits r into subranges r and *this
    R( R& r, split );
    // Splits r into subranges r and *this in proportion p
    R( R& r, proportional_split p );
    // Allows usage of proportional splitting constructor
    static const bool is_splittable_in_proportion = true;
    ...
};

全部归结为以下代码:

#include <mutex>
#include <iostream>
#include <thread>

#include <tbb/parallel_for.h>
#include <tbb/task_scheduler_init.h>

std::mutex cout_mutex;

int main()
{
    auto N = 8;

    tbb::task_scheduler_init init(4);

    tbb::parallel_for(tbb::blocked_range<int>(0, N),
        [&](const tbb::blocked_range<int>& r)
        {
            for (int j = r.begin(); j < r.end(); ++j) {
                // Compute A[j]
                std::this_thread::sleep_for(std::chrono::seconds(1));
                cout_mutex.lock();
                std::cout << std::this_thread::get_id()<< ", " << j << std::endl;
                cout_mutex.unlock();
            }
        }
    );
}

上面的代码给出:

140455557347136, 0
140455526110976, 4
140455521912576, 2
140455530309376, 6
140455526110976, 5
140455557347136, 1
140455521912576, 3
140455530309376, 7

但是我想要类似的东西:

140455557347136, 0
140455526110976, 1
140455521912576, 2
140455530309376, 3
140455526110976, 5
140455557347136, 4
140455521912576, 6
140455530309376, 7

关于迭代对象的任何建议还是有更好的解决方案?

1 个答案:

答案 0 :(得分:1)

考虑使用外部原子,例如(// !!!标记换行)

#include <mutex>
#include <iostream>
#include <thread>

#include <tbb/parallel_for.h>
#include <tbb/task_scheduler_init.h>

#include <atomic>                                 // !!!

std::mutex cout_mutex;

int main()
{
    auto N = 8;

    tbb::task_scheduler_init init(4);

    std::atomic<int> monotonic_begin{0};           // !!!

    tbb::parallel_for(tbb::blocked_range<int>(0, N),
        [&](const tbb::blocked_range<int>& r)
        {
            int s = static_cast<int>(r.size());    // !!!
            int b = monotonic_begin.fetch_add(s);  // !!!
            int e = b + s;                         // !!!
            for (int j = b; j < e; ++j) {          // !!!       
                // Compute A[j]
                std::this_thread::sleep_for(std::chrono::seconds(1));
                cout_mutex.lock();
                std::cout << std::this_thread::get_id() << ", " << j << std::endl;
                cout_mutex.unlock();
            }
        }
    );
}

该方法给出:

15084, 0
15040, 3
12400, 2
11308, 1
15084, 4
15040, 5
12400, 6
11308, 7

为什么具有单调性很重要?您可能需要考虑使用parallel_pipeline或流程图来指定计算依赖性。