如何正确使用C ++ stl并行性

时间:2017-09-18 00:54:06

标签: c++ parallel-processing stl

所以我正在查找如何使用stl c ++的东西做一些并行操作,并在Stack Overflow中的另一个问题上找到以下代码

template <typename RAIter> //FOUND ON STACK OVERFLOW
int parallel_sum(RAIter beg, RAIter end)
{
    auto len = end - beg;
    if (len < 1000)
        return std::accumulate(beg, end, 0);

    RAIter mid = beg + len / 2;
    auto handle = std::async(std::launch::async,
        parallel_sum<RAIter>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}

我想制作一个通用的parallel_for_each函数,它循环一个(希望是)任意容器类型并将算法应用于每个条目,所以我将上面的内容修改为:

template <typename ContainerIterator, typename containerSizeType, typename AlgorithmPerEntry> //modified version of parallel sum code above : https://stackoverflow.com/questions/36246300/parallel-loops-in-c
void parallel_for_each(ContainerIterator beg, ContainerIterator end, AlgorithmPerEntry& algorithm, containerSizeType maxProbSize)
{   
    containerSizeType len = end - beg;
    if (len < maxProbSize){//if you are sufficiently small, go ahead and execute
        std::for_each(beg, end, algorithm);
        std::cout << "working on processor with id = " << GetCurrentProcessorNumber() << std::endl;//the processor id's change so I'm assuming this is executing in parallel
        return;
    }
    //otherwise, continue spawning more threads
    ContainerIterator mid = beg + len / 2;
    auto handle = std::async(std::launch::async,
        parallel_for_each<ContainerIterator, containerSizeType, AlgorithmPerEntry>, mid, end, algorithm, maxProbSize);
    parallel_for_each(beg, mid, algorithm, maxProbSize);
    handle.get(); //corrected as advised
}

我想测试的是一个超级简单的仿函数,所以我做了以下内容:

template<typename T>
struct dataSetter
{
    const T& set_to;
    dataSetter(const T& set_to_in) : set_to(set_to_in){}

    void operator()(T& set_this)
    {
        set_this = set_to;
    }
};

非常简单,只需将某些arg的值设置为其operator()

即可

这是我的主要功能的主体

std::vector<int> ints(100000);
unsigned minProbSize = 1000;
int setval = 7;

dataSetter<int> setter(setval);

parallel_for_each(ints.begin(), ints.end(), setter, minProbSize);//parallel assign everything to 7
//some sort of wait function to go here?
std::cout << std::endl << "PS sum of all ints = " << parallel_sum(ints.begin(), ints.end()) << std::endl; //parallel sum the entries

int total = 0;//serial sum the entries
for (unsigned i = 0; i < ints.size(); i++)
    total += ints[i];

std::cout << std::endl << "S sum of all ints = " << total << std::endl;
std::cout << std::endl << "PS sum of all ints = " << parallel_sum(ints.begin(), ints.end()) << std::endl; //parallel sum the entries again

以下是一些输出:

PS sum of all ints = 689052

S sum of all ints = 700000

PS sum of all ints = 700000

另一次运行的输出:

PS sum of all ints = 514024

S sum of all ints = 700000

PS sum of all ints = 700000

它始终获得向量低的第一个并行和。我猜测发生了什么是所有的赋值线程都被创建,然后求和线程被创建,但是某些求和线程正在过早地执行(在最后一个赋值线程之前)。有什么方法可以迫使我等一下吗?和往常一样,我对所有建议持开放态度。

1 个答案:

答案 0 :(得分:1)

MSVS 2013附带非标准兼容的std异步。据我所知,这是故意的。

当使用异步启动策略调用时,此非兼容的std异步无法返回阻止任务完成的期货。

结果是你的代码是正确的,但你的编译器坏了。要么升级到2015/2017,要么在它超出范围之前添加handle.get()

就个人而言,我会以不同的方式编写这样的实用程序。

auto launch_async = [](auto&& f){
  return std::async( std::launch::async, decltype(f)(f) );
};
template <class Linear, class Executor=decltype(launch_async) const&>
auto parallel_algo(Linear&& linear, std::size_t chunk_size, Executor&& exec=launch_async){
  return 
    [
      linear=std::forward<Linear>(linear),
      chunk_size,
      exec=std::forward<Executor>(exec)
    ]
    ( auto start, auto finish )
    {
    std::size_t count = finish-start;
    if (count <= chunk_size) {
      linear( start, finish);
      return;
    }
    std::size_t par = (count+chunk_size-1)/chunk_size;
    std::vector<std::future<void>> tasks( par-1 );
    auto task=[&]( auto i ){
      auto b = start+( count*i/par );
      auto e = start+( count*(i+1)/par );
      return [b,e,linear]{ linear(b,e); };
    };
    for(auto& f:tasks){
      auto i = &f-tasks.data();
      f = exec( task(i) );
    }
    task(par-1)();
    for (auto&f:tasks) f.get();
  };
}
template<class F>
auto foreacher( F&&f ){
    return [f=std::forward<F>(f)]( auto b, auto e ){
      for (auto i=b; i!=e; ++i) f(*i);
    };
}

是C ++ 14,但可以在C ++ 11中进行模拟。

这样做是采用线性算法和最大块大小,返回并行算法。

在这两种情况下,算法都采用迭代器范围。

当我在那里时,并行算法工厂需要一个执行程序。您可以编写一个exectuor,例如一个线程池,以避免不必要地生成太多线程。单元素算法使用foreacher将自己提升到范围算法。

一些玩具执行者:

auto launch_deferred = [](auto&& f){
  return std:async( std::launch::deferred, decltype(f)(f) );
};
template<class F, class R=std::result_of_t< decltype(f)() >>
std::enable_if_t< !std::is_same<R,void>, std::future<R> >
make_ready_future( F&& f ) {
  std::promise<R> p;
  try {
    p.set_value( decltype(f)(f)() );
  } catch( ... ) {
    p.set_exception(std::current_exception());
  }
  return p.get_future();
}
template<class F, class R=std::result_of_t< decltype(f)() >>
std::enable_if_t< std::is_same<R,void>, std::future<R> >
make_ready_future( F&& f ) {
  std::promise<void> p;
  try {
    decltype(f)(f)();
    p.set_value();
  } catch( ... ) {
    p.set_exception(std::current_exception());
  }
  return p.get_future();
}

auto launch_ready = [](auto&& f){
  return make_ready_future( decltype(f)(f) );
};

这两个都使你的并行代码在一个线程中运行。

一个更高级的人在线程池中排队任务并同样返回期货。

这是测试代码:

std::vector<int> v(10000);
parallel_algo( foreacher([](auto&x){x=7;}), 100 )( v.begin(), v.end() );
std::atomic<int> total(0);
parallel_algo( foreacher([&total](auto&x){total+=x;}), 100 )( v.begin(), v.end() );
std::cout << total << "\n";