(原创)发布一个c++11开发的轻量级的并行Task库TaskCpp
TaskCpp简介
TaskCpp是c++11开发的一个跨平台的并行task库,它的设计思路来源于微软的并行计算库ppl和intel的并行计算库tbb,关于ppl和tbb我在前面有介绍。既然已经有了这两个大公司开发的并行计算库,我为什么还要开发自己的并行计算库。有两个原因:
- ppl只能在windows上用不能跨平台,tbb能跨平台,但是受限于原始设计,tbb的task比较弱没有ppl的强大,所以他们不能完全满足我的要求;
- 我觉得可以用c++11可以开发出一个轻量级的好用的并行task库。
TaskCpp在接口设计上尽量和ppl保持一致,因为我觉得ppl的接口很好很强大。因此,TaskCpp的接口用法和语义和ppl基本是一致的。因为TaskCpp是一个轻量级的task库,总共也不过三百多行代码,本着简单够用的原则,只提供了一些和ppl类似的常用用法, 有些不常用的特性不考虑支持。比如,不支持任务的取消,因为加入任务的取消会导致增加很多复杂性,而实际中用得比较少,所以不考虑支持,够用就好。
支持的平台
需要支持c++11的编译器,建议编译器:
- linux: GCC4.7+
- windows: vs2012 nov ctp+, 最好是vs2013
库的使用
使用TaskCpp仅仅需要包含头文件即可,在程序中使用只需要包含#include <TaskCpp.h>和少量的boost头文件即可。
TaskCpp的功能
TaskCpp提供一下功能:
- 并行任务:一种并行执行若干工作任务的机制。
- 基本的异步任务
- 延续的任务
- 组合任务
- WhenAll
- WhenAny
- 任务组
- 并行算法:并行作用于数据集合的泛型算法。
- ParallelForeach算法
- ParallelInvoke算法
- ParallelReduce算法
TaskCpp用法介绍
并行任务
基本的异步任务Task
Task会创建一个异步操作,这个异步操作发起方式是延迟加载方式发起的,即在调用Task的Wait或者Get时才真正发起异步操作。Task可以通过std::function或者lambda表达式去创建,不支持直接原生函数创建,如果要用原生函数需要先通过lambda或者std::function包装一下。Task的Wait接口只是等待异步操作结束。Task的Get接口接收参数并等待异步操作结束并返回结果。PPL中的get接口是不能接收参数的,TaskCpp的Get接口是可以接受任意参数的,更灵活一点,算是较PPL的一个小优点吧。下面是Task的基本用法:
#include <TaskCpp.h>
using namespace Cosmos; void TestTask()
{
Task<void()> task([]{cout << << endl; });
task.Wait(); Task<void()> task1 = []{cout << << endl; };
task1.Wait(); Task<int()> task2 = []{cout << << endl; return ; };
cout << task2.Get() << endl; Task<int(int)> task3 = [](int i){cout << i << endl; return i; };
cout << task3.Get() << endl;
}
组合任务--WhenAll
WhenAll保证一个任务集合中所有的任务完成。WhenAll函数会生成一个任务,该任务可在完成一组任务之后完成。 此函数可返回一个std::vector 对象,该对象包含集合中每个任务的结果。 以下基本示例使用WhenAll创建表示完成其他三个任务的任务。下面是WhenAll的基本用法:
void PrintThread()
{
cout << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds());
} void TestWhenAll()
{
vector<Task<int()>> v = {
Task<int()>([]{PrintThread(); std::this_thread::sleep_for(std::chrono::seconds()); return ; }),
Task<int()>([]{PrintThread(); return ; }),
Task<int()>([]{PrintThread(); return ; }),
Task<int()>([]{PrintThread(); return ; })
}; cout << "when all " << endl;
WhenAll(v).Get(); }
注意:WhenAll是非阻塞的,它只是创建一个任务,在Wait或Get时才发起异步操作。传递给WhenAll的任务必须是统一的。 换言之,它们必须都返回相同类型。
组合任务--WhenAny
WhenAny在任务集合中任意一个任务结束之后就返回。函数会生成一个任务,该任务可在完成一组任务的第一个任务之后完成。 此函数可返回一个 std::pair 对象,该对象包含已完成任务的结果和集合中任务的索引。下面是WhenAny的基本用法:
void TestWhenAny()
{
vector<Task<int()>> v = {
Task<int()>([]{PrintThread(); std::this_thread::sleep_for(std::chrono::seconds()); return ; }),
Task<int()>([]{PrintThread(); return ; }),
Task<int()>([]{PrintThread(); return ; }),
Task<int()>([]{PrintThread(); return ; })
}; cout << "when any " << endl;
WhenAny(v).Then([](std::pair<int, int>& result)
{
cout << " index " << result.first << " result " << result.second << endl;
return result.second;
}).Then([](int result){cout << "any result: " << result << endl; }).Get();
}
注意:WhenAny是非阻塞的,它只是创建一个任务,在Wait或Get时才发起异步操作。传递给WhenAny的任务必须是统一的。 换言之,它们必须都返回相同类型。
任务组--TaskGroup
TaskGroup可以并行的处理一组任务,TaskGroup可以接受多个task或者function,TaskGroup的Wait等待所有任务完成。下面是TaskGroup的基本用法:
void TestTaskGroup()
{
Task<int()> t1([]{PrintThread(); return ; });
Task<double()> t2([]{PrintThread(); return 2.123; });
Task<void()> t3([]{PrintThread(); });
Task<string()> t4([]{PrintThread(); return "ok"; }); TaskGroup group;
group.Run(t1);
group.Run(t2);
group.Run(t3);
group.Run(t4); //如果你觉得这样一个一个Run加入任务,你也可以一起Run
group.Run(t1, t2, t3, []{PrintThread(); return ; }); group.Wait();
}
PPL的task_group的任务只能是void()形式的,TaskCpp允许一些简单类型的任务如int()、double()、string()等,其实任务的返回类型没有实际意义,因为Wait没有返回值,这里支持多种返回类型的任务只不过是为了减少一点限制,用起来稍微方便一点罢了。PPL加入任务只能一个一个Run,要加入多个任务时有点繁琐,TaskCpp可以一次Run多个任务,比PPL要方便一些。这两点算是较PPL的两个小优点吧。
并行算法
ParallelForeach算法
ParallelForeach算法与 STL std::for_each 算法类似,只是 parallel_for_each 算法并发执行任务。用法比较简单:
bool check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{
for (int i = ; i < x; ++i)
if (x % i == )
return false;
return true;
} void TestParallelFor()
{
vector<int> v;
for (int i = ; i < ; i++)
{
v.push_back(i + );
} boost::timer t; ParallelForeach(v.begin(), v.end(), check_prime);
ParallelForeach(v.begin(), v.end(), check_prime); cout << "taskcpp: " << t.elapsed() << endl;
}
ParallelInvoke算法
ParallelInvoke算法并行执行一组任务。 在完成所有任务之前,此算法不会返回。 当您需要同时执行多个独立的任务时,此算法很有用。ParallelInvoke和TaskGroup的作用是一样的。用法比较简单:
void TestParaInvoke()
{
auto f = []{cout << "" << endl; return ; };
ParallelInvoke(f, []{cout << "" << endl; });
}
ParallelReduce算法
ParallelReduce算法在实际应用中比较常用,有点类似于map-reduce,可以并行的对一个集合进行reduce操作。ParallelReduce的用法稍微复杂一点,它的原型:
- ParallelReduce(range,init, reduceFunc);
- ParallelReduce(range,init, rangeFunc, reduceFunc);
第一个参数是集合,第二个参数是算法的初始值,第三个参数rangeFunc是一个声称中间结果的函数,第四个参数是中间结果的汇聚函数。如果调用ParallelReduce(range,init, reduceFunc),则表示rangeFunc和reduceFunc是一个函数。
下面的例子是并行的计算100000000个整数的和:
void TestParallelSum()
{
vector<int> v;
const int Size = ;
v.reserve(Size);
for (int i = ; i < Size; i++)
{
v.push_back(i + );
} int i = ; boost::timer t;
auto r = ParallelReduce(v, i, [](const vector<int>::iterator& begin, vector<int>::iterator&end, int val)
{
return std::accumulate(begin, end, val);
});
cout << t.elapsed() << " " << r << endl;
}
下面是并行查找最长的字符串的例子:
void TestFindString()
{
vector<string> v;
v.reserve();
for (int i = ; i < ; i++)
{
v.emplace_back(std::to_string(i + ));
} string init = ""; auto f = [](const vector<string>::iterator& begin, vector<string>::iterator&end, string& val)
{
return *std::max_element(begin, end, [](string& str1, string& str2){return str1.length()<str2.length(); });
}; boost::timer t;
auto r = ParallelReduce(v, init, f, f);
cout << t.elapsed() << " " << r << endl;
}
性能
用四个测试用例对比测试了tbb、ppl、TaskCpp和单线程的性能。下图是测试对比的结果:

可以看到TaskCpp的性能比单线程效率要高,总体上也优于ppl和tbb,其中ppl和tbb在某些场景下性能还不如单线程高,所以在使用时要以实际测试数据为准,并不是一用并行库效率就能提高。
下载地址
TaskCpp开源协议
遵循LGPL(GNU General Public License)协议。
注意
TaskCpp是一个任务库,不是线程池,每启动一个task就会创建一个线程,如果需要线程池可以看这里。
联系我
如果发现问题或者有什么建议请给我留言或者发邮件qicosmos@163.com .
c++11 boost技术交流群:296561497,欢迎大家来交流技术。
后记
TaskCpp的开发和测试花费了我两三周的时间,开发之初我就计划将其开源,我希望更多的人能用起来并推广TaskCpp,促进它的发展。曾经有人问我,为什么坚持发原创文章分享技术,是不是有什么好处。我一下子还真答不上来,因为我根本就没有想过有啥好处,现在再想一下,好处嘛,分享的术也许对别人学习有帮助吧。再想想我这样做的原因,一个原因是兴趣,这要感谢c++11,是c++11让我觉得c++语言是非常有意思和有魅力的语言,总能带给人惊喜,没有c++11我也不可能完成TaskCpp库。还有就是一点点分享快乐的精神,我分享我快乐。最重要的原因是一点点梦想,c++中开源库太少了,很多框架和基础库都还不够,远远赶不上java,所以在使用和推广上不如java。但是我有一点梦想:我希望通过自己的一点努力能让c++的世界变得更加美好,能让c++开发者的日子变得美好。是的,正是这个梦想促使我将我开发的大部分代码都开源出来!也正是这个梦想促使我坚持写原创博客分享技术!
(原创)发布一个c++11开发的轻量级的并行Task库TaskCpp的更多相关文章
- 一个让业务开发效率提高10倍的golang库
一个让业务开发效率提高10倍的golang库 此文除了是标题党,没有什么其他问题. 这篇文章推荐一个库,https://github.com/jianfengye/collection. 这个库是我在 ...
- VisualSVN 4.0.11补丁原创发布
VisualSVN 4.0.11补丁原创发布 目前是官方最新版本.
- iOS 11开发教程(七)编写第一个iOS11代码Hello,World
iOS 11开发教程(七)编写第一个iOS11代码Hello,World 代码就是用来实现某一特定的功能,而用计算机语言编写的命令序列的集合.现在就来通过代码在文本框中实现显示“Hello,World ...
- iOS 11开发教程(三)运行第一个iOS 11程序
iOS 11开发教程(三)运行第一个iOS 11程序 运行iOS11程序 创建好项目之后,就可以运行这个项目中的程序了.单击运行按钮,如果程序没有任何问题的话,会看到如图1.6和1.7的运行效果. 图 ...
- iOS 11开发教程(二)编写第一个iOS 11应用
iOS 11开发教程(二)编写第一个iOS 11应用 编写第一个iOS 11应用 本节将以一个iOS 11应用程序为例,为开发者讲解如何使用Xcode 9.0去创建项目,以及iOS模拟器的一些功能.编 ...
- 如何开发和发布一个Vue插件
前言 Vue 项目开发过程中,经常用到插件,比如原生插件 vue-router.vuex,还有 element-ui 提供的 notify.message 等等.这些插件让我们的开发变得更简单更高效. ...
- iOS 11开发教程(一)
iOS 11开发概述 iOS 11是目前苹果公司用于苹果手机和苹果平板电脑的最新的操作系统.该操作系统的测试版于2017年6月6号(北京时间)被发布.本章将主要讲解iOS 11的新特性.以及使用Xco ...
- C++ 11开发环境的搭建(Windows Platform)
C++ 11开发环境的搭建(Windows Platform) Code::Block IDE:Code::Blocks 12.11版本号 Compiler:TDM-GCC http: ...
- 学习python这么久,有没有考虑发布一个属于自己的模块?
1. 为什么需要对项目分发打包? 平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 打包. 打包,就是 ...
随机推荐
- iOS 实现加载转圈效果
1.思路: 新建一个view,添加shape,给予一个动画实现. 2.效果图: 效果1: 效果2: gif有点卡,代码运行不会这样. 3.源码(整个类放进来的) 效果1源码: // // YJDown ...
- 【RS】CoupledCF: Learning Explicit and Implicit User-item Couplings in Recommendation for Deep Collaborative Filtering-CoupledCF:在推荐系统深度协作过滤中学习显式和隐式的用户物品耦合
[论文标题]CoupledCF: Learning Explicit and Implicit User-item Couplings in Recommendation for Deep Colla ...
- Jetty使用内存过大的解决方案
之前用Jetty做过一个消息通知服务器,主要功能就是其他各个子系统如果有需要push给客户端消息的就把这个消息发给我的Server,我用WebSocket来推送给客户端~ 程序上线一段时间之后运维工程 ...
- 关于ARP协议
什么是arp协议: arp协议是地址解析协议,英文是address resolution protocol 通过IP地址可以获得mac地址 两个主机的通信归根到底是MAC地址之间的通信 在TCP/IP ...
- idea 不下载jar包
是因为用的gradle 然后没有设置gradle jvm
- Python之包管理工具
安装Python包的过程中,经常涉及到distutils.setuptools.distribute.setup.py.easy_install.easy_install和pip等等. distuil ...
- Fix SCRIPT5009: “RegisterSod” undefined error
When I set up development environment for apps for SharePoint 2013 and quickly get apps from SharePo ...
- 第二篇:呈现内容_第二节:WebControl呈现
一.WebControl的呈现过程 WebControl派生自Control类,所以WebControl的呈现功能基于Control的呈现逻辑之上,但有了比较大的扩展. 首先,WebControl重写 ...
- C#基础课程之一注释和控制台、一些常识
注释是程序员对代码的说明,以使程序具有可读性.源代码在编译的过程中,编译器会忽略其注释部分的内容. ()行注释 格式为:// 注释内容 用两个斜杠表示注释的开始,直到该行的结尾注释结束. ()块注释 ...
- Hudson基本工作原理
从SVN下载代码到hudson服务器本地 -> 将SVN下载的源代码,利用maven[maven依赖pom.xml]或者ant[ant依赖build.xml]打包(war包),pom.xml ...