C++ 并发编程学习(五)

  • 时间:2018-12-24 22:37 作者:Zbertj 来源:Zbertj 阅读:548
  • 扫一扫,手机访问
摘要:运行时决定线程数量一. 原生并行版的 std::accumulate??std::thread::hardware_concurrency() 在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提

运行时决定线程数量

一. 原生并行版的 std::accumulate

??std::thread::hardware_concurrency() 在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提醒,当系统信息无法获取时,函数也会返回0。
??代码中将整体工作拆分成小任务交给每个线程去做,其中设置最小任务数,是为了避免产生太多的线程。程序可能会在操作数量为0的时候抛出异常。比方, 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)//26{  unsigned long const length=std::distance(first,last);  if(!length){  //1    return init;  }  unsigned long const min_per_thread=25;  //线程(块)中最小任务数  unsigned long const max_threads=(length+min_per_thread-1)/min_per_thread; // 2  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; // 4  std::vector<T> results(num_threads);  std::vector<std::thread> threads(num_threads-1); // 5  Iterator block_start=first;  for(unsigned long i=0; i < (num_threads-1); ++i)  {    Iterator block_end=block_start;    std::advance(block_end,block_size); // 6    threads[i]=std::thread(accumulate_block<Iterator,T>(),block_start,block_end,std::ref(results[i]));    block_start=block_end; // 8  }  accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]); // 9  std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); // 10  return std::accumulate(results.begin(),results.end(),init); //11}

函数看起来很长,但不复杂。假如输入的范围为空①,就会得到init的值。反之,假如范围内多于一个元素时,都需要用范围内元素的总数量除以线程(块)中最小任务数,从而确定启动线程的最大数量②,这样能避免无谓的计算资源的白费。比方,一台32芯的机器上,只有5个数需要计算,却启动了32个线程。计算量的最大值和硬件支持线程数中,较小的值为启动线程的数量③。由于上下文频繁的切换会降低线程的性能,所以你一定不想启动的线程数多于硬件支持的线程数量。当 std::thread::hardware_concurrency() 返回0,你可以选择一个合适的数作为你的选择;在本例中,我选择了"2"。你也不想在一台单核机器上启动太多的线程,由于这样反而会降低性能,有可能最终让你放弃使用并发。每个线程中解决的元素数量,是范围中元素的总量除以线程的个数得出的④。对于分配能否得当,我们会在后面探讨。现在,确定了线程个数,通过创立一个 std::vector<T> 容器存放中间结果,并为线程创立一个 std::vector<std::thread> 容器⑤。这里需要注意的是,启动的线程数必需比num_threads少1个,由于在启动之前已经有了一个线程(主线程)。使用简单的循环来启动线程:block_end迭代器指向当前块的末尾⑥,并启动一个新线程为当前块累加结果⑦。当迭代器指向当前块的末尾时,启动下一个块⑧。启动所有线程后,⑨中的线程会解决最终块的结果。对于分配不均,由于知道最终块是哪一个,那么这个块中有多少个元素就无所谓了。当累加最终块的结果后,可以等待 std::for_each ⑩创立线程的完成(好像在清单2.7中做的那样),之后使用 std::accumulate 将所有结果进行累加?。结束这个例子之前,需要明确:T类型的加法运算不满足结合律(比方,对于float型或者double型,在进行加法操作时,系统很可能会做截断操作),由于对范围中元素的分组,会导致parallel_accumulate得到的结果可能与 std::accumulate 得到的结果不同。同样的,这里对迭代器的要求更加严格:必需都是向前迭代器,而 std::accumulate 可以在只传入迭代器的情况下工作。对于创立出results容器,需要保证T有默认构造函数。对于算法并行,通常都要这样的修改;不过,需要根据算法本身的特性,选择不同的并行方式。算法并行会在第8章有更加深入的探讨。需要注意的:由于不能直接从一个线程中返回一个值,所以需要传递results容器的引用到线程中去。另一个办法,通过地址来获取线程执行的结果;第4章中,我们将使用期望(futures)完成这种方案。

当线程运行时,所有必要的信息都需要传入到线程中去,包括存储计算结果的位置。不过,并非总需如此:有时候这是识别线程的可行方案,可以传递一个标识数,例如清单2.7中的i。不过,当需要标识的函数在调用栈的深层,同时其余线程也可调用该函数,那么标识数就会变的捉襟见肘。好消息是在设计C++的线程库时,就有预见了这种情况,在之后的实现中就给每个线程附加了唯一标识符。

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|软件环境】梦幻仙域游戏攻略(2023-12-19 10:02)
【系统环境|软件环境】梦幻仙域游戏攻略(2023-12-19 10:02)
【系统环境|】卡帕部落揭秘潮玩新宠,探究玩法(2023-12-14 09:45)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】如何开发搭建卡帕部落模式源码(2023-12-12 10:44)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
手机二维码手机访问领取大礼包
返回顶部