在运行时选择线程数量

        C++标准库中对此有所帮助的特性是 std::thread::hardware_currency()这个函数返回一个对于给定程序执行时能够真正并发运行的线程数量的指示。例如,在多核系统上它可能是 CPU 核心的数量。它仅仅是一个提示,如果该信息不可用则函数可能会返回0,但它对于在线程间分割任务是一个有用的指南。下面将展示std::accumulate的一个简单的并行版本实现。它在线程之间划分所做的工作,使得每个线程具有最小数目的元素以避免过多线程的开销。请注意,该实现假定所有的操作都不引发异常,即便异常可能会发生。例如,std::thread构造函数如果不能启动一个新的执行线程那么它将引发异常。在这样的算法中处理异常超出了这个简单示例的范围,后面我会继续解答。

template<typename Iterator,typename T>
struct accumulate_block{
    void operator()(Iterator first,Iterator last,T& result){
        result=std::accumulate(first,last,result);
}
template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init){
    unsiqned long const length=std::distance(first,last);
    if(!length)
        return init;
    unsigned long const min_per_thread=25;
    unsigned long const max_threads=
(length+min_per_thread-1)/min_per_thread;
    unsigned long const hardware_threads=std::thread::hardware_concurrency();
    unsigned long const num_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads);
    unsigned long const block_size=length/num_threads;
    std::vector<T> results(num_threads);
    std::vector<std::thread> threads(num_threads-1);
    Iterator block_start=first;
    for(unsigned long i=0;i<(numthreads-1);++i){
        Iterator block_end=block_start;
        std::advance(block_end,block_size);
        threads[i]=sta::thread(accumulate_block<Iterator,T>()block_start,block_end,std::ref(results[i]));
        block_start=block end;
    }
accumulate block<Iterator,T>()(block_start,last,results[num_threads-1]);
std::foreach(threads.begin(),threads.end(),std::mem_fn(&std::thread::join));
    
return std::accumulate(results.begin(),results.end(),init);
}

        其中,函数模板std :: mem_fn生成指向成员的指针的包装对象,该对象可以存储,复制和调用指向成员的指针。 调用std :: mem_fn时,可以使用对象的引用和指针(包括智能指针)。

        虽然上面代码这是一个相当长的函数,但它实际上是很直观的。如果输人范围为空0,只返回初始值 init。否则,此范围内至少有一个元素,于是你将要处理的元素数量除以最小的块大小,以获取线程的最大数量 。这是为了避免当范围中只有五个值时,在一个32核的机器上创建32个线程。要运行的线程数是你计算出的最大值和硬件线程数量的较小值。你不会想要运行比硬件所能支持的更多的线程(超额订阅,oversubscription),因为上下文切换将意味着更多的线程会降低性能。如果对 std::thread::hardware concurrency()的调用返回0,你只需简单地替换上你所选择的数量,在这个例子中我选择了2。你不会想要运行过多的线程,因为在单核的机器上这会使事情变慢,但同样地你也不希望运行的过少,因为那样的话,你就会错过可用的并发。
        每个待处理的线程的条目数量是范围的长度除以线程的数量 。如果你担心数量不能整除,没必要一稍后再来处理。

        既然你知道有多少个线程,你可以为中间结果创建一个 std::vector<T>,同时为线程创建一个 std::vector<std::thread>。请注意,你需要启动比num_threads少一个的线程,因为已经有一个了。启动线程是个简单的循环:递进 block_end 选代器到当前块的结尾,并启动一个新的线程来累计此块的结果。下一个块的开始是这一个的结束。
        当你启动了所有的线程后,这个线程就可以处理最后的块。这就是你处理所有未被整除的地方。你知道最后一块的结尾只能是 last,无论在那个块里有多少元素。一旦累计出最后一个块的结果,你可以等待所有使用 std::for_each 生成的线程,接着通过最后调用 std::accumulate 将结果累加起来。
        在你离开这个例子前,值得指出的是在类型的加法运算符不满足结合律的地方(如float和double),这个parallel_accumulate 的结果可能会跟std::accumulate的有所出入,这是将范围分组成块导致的。此外,对迭代器的需求要更严格一些,它们必须至少是前向迭代器(forward_iterators),然而std::accumulate可以和单通输入选代器(input_iterators)一起工作,同时T必须是可默认构造的(default constructible)以使得你能够创建 results 向量。这些需求的各种变化是并行算法很常见的,就其本质而言,它们以某种方式的不同是为了使其并行,并且在结果和需求上产生影响。并行算法会在后面中进行更深入的阐述。另外值得一提的是,因为你不能直接从一个线程中返回值,所以你必须将相关项的引用传入 results 向量中。从线程中返回结果的替代方法,会通过使用future来实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值