21

C++ 2011 包含非常酷的新特性,但我找不到很多并行化 for 循环的示例。所以我非常幼稚的问题是:如何将简单的 for 循环(例如使用“omp parallel for”)与 std::thread 并行化?(我搜索一个例子)。

非常感谢你。

4

6 回答 6

32

std::thread不一定意味着并行循环。它旨在作为构建类似 parallel_for 算法的结构的低级抽象。如果你想并行化你的循环,你应该自己编写一个 parallel_for 算法,或者使用提供基于任务的并行性的现有库。

以下示例显示了如何使简单循环并行化,但另一方面也显示了缺点,例如缺少负载平衡和简单循环的复杂性。

  typedef std::vector<int> container;
  typedef container::iterator iter;

  container v(100, 1);

  auto worker = [] (iter begin, iter end) {
    for(auto it = begin; it != end; ++it) {
      *it *= 2;
    }
  };


  // serial
  worker(std::begin(v), std::end(v));

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 200

  // parallel
  std::vector<std::thread> threads(8);
  const int grainsize = v.size() / 8;

  auto work_iter = std::begin(v);
  for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
    *it = std::thread(worker, work_iter, work_iter + grainsize);
    work_iter += grainsize;
  }
  threads.back() = std::thread(worker, work_iter, std::end(v));

  for(auto&& i : threads) {
    i.join();
  }

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 400

使用提供parallel_for模板的库,可以将其简化为

parallel_for(std::begin(v), std::end(v), worker);
于 2012-05-29T09:17:35.343 回答
4

很明显,这取决于你的循环做什么,你如何选择并行化,以及你如何管理线程的生命周期。

我正在阅读来自 std C++11 线程库的书(这也是boost.thread维护者之一并编写了Just Thread),我可以看到“它取决于”。

现在,为了让您了解使用新标准线程的基础知识,我建议您阅读这本书,因为它提供了大量示例。另外,看看http://www.justsoftwaresolutions.co.uk/threading/https://stackoverflow.com/questions/415994/boost-thread-tutorials

于 2012-05-29T08:53:25.170 回答
3

无法提供 C++11 特定的答案,因为我们仍然主要使用 pthreads。但是,作为与语言无关的答案,您可以通过将其设置为在单独的函数(线程函数)中运行来并行化某些东西。

换句话说,你有一个类似的功能:

def processArraySegment (threadData):
    arrayAddr = threadData->arrayAddr
    startIdx  = threadData->startIdx
    endIdx    = threadData->endIdx

    for i = startIdx to endIdx:
        doSomethingWith (arrayAddr[i])

    exitThread()

并且,在您的主代码中,您可以分两块处理数组:

int xyzzy[100]

threadData->arrayAddr = xyzzy
threadData->startIdx  = 0
threadData->endIdx    = 49
threadData->done      = false
tid1 = startThread (processArraySegment, threadData)

// caveat coder: see below.
threadData->arrayAddr = xyzzy
threadData->startIdx  = 50
threadData->endIdx    = 99
threadData->done      = false
tid2 = startThread (processArraySegment, threadData)

waitForThreadExit (tid1)
waitForThreadExit (tid2)

(请记住,您应该确保线程 1在主线程开始为线程 2 修改数据之前已将数据加载到其本地存储中,可能使用互斥锁或使用结构数组,每个线程一个)。

换句话说,仅仅修改一个for循环以使其并行运行很少是一件简单的事情,尽管这样会很好,例如:

for {threads=10} ({i} = 0; {i} < ARR_SZ; {i}++)
    array[{i}] = array[{i}] + 1;

相反,它需要重新安排代码以利用线程。

而且,当然,您必须确保并行处理数据是有意义的。如果您将每个数组元素设置为前一个加 1,那么再多的并行处理也无济于事,因为您必须先等待前一个元素被修改。

上面的这个特定示例只是使用传递给线程函数的参数来指定它应该处理数组的哪一部分。线程函数本身包含执行工作的循环。

于 2012-05-29T01:58:44.607 回答
3

使用这个类,你可以这样做:

Range based loop (read and write)
pforeach(auto &val, container) { 
  val = sin(val); 
};

Index based for-loop
auto new_container = container;
pfor(size_t i, 0, container.size()) { 
  new_container[i] = sin(container[i]); 
};
于 2013-08-06T07:49:26.180 回答
3

使用 std::thread 和 lambda 表达式定义宏:

#ifndef PARALLEL_FOR
#define PARALLEL_FOR(INT_LOOP_BEGIN_INCLUSIVE, INT_LOOP_END_EXCLUSIVE,I,O)          \                                                               \
    {                                                                               \
        int LOOP_LIMIT=INT_LOOP_END_EXCLUSIVE-INT_LOOP_BEGIN_INCLUSIVE;             \
        std::thread threads[LOOP_LIMIT]; auto fParallelLoop=[&](int I){ O; };       \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i]=std::thread(fParallelLoop,i+INT_LOOP_BEGIN_INCLUSIVE);       \
        }                                                                           \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i].join();                                                      \
        }                                                                           \
    }                                                                               \
#endif

用法:

int aaa=0; // std::atomic<int> aaa;
PARALLEL_FOR(0,90,i,
{
    aaa+=i;
});

它丑陋但有效(我的意思是多线程部分,而不是非原子递增)。

于 2017-07-15T10:51:13.963 回答
1

如果您确定不可能进行并发访问,AFAIK 并行化循环的最简单方法是使用 OpenMP。

除 LLVM(截至 2013 年 8 月)外,所有主要编译器都支持它。

例子 :

for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

这将很容易并行化,如下所示:

#pragma omp parallel for
for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

但是,请注意,这仅对大量值有效。

如果您使用 g++,另一种非常 C++11-ish 的做法是使用 lambda 和 for_each,并使用 gnu 并行扩展(可以在后台使用 OpenMP):

__gnu_parallel::for_each(std::begin(tab), std::end(tab), [&] () 
{
    stuff_of_your_loop();
});

但是,for_each 主要用于数组、向量等...但是如果您只想通过创建一个Range类来迭代一个范围,那么您可以“欺骗”它,begin并且end方法主要会增加一个 int。

请注意,对于执行数学运算的简单循环,其中的算法都可以与 G++ 并行化#include <numeric>#include <algorithm>

于 2013-08-06T05:38:57.397 回答