并行编程实战——TBB中的Task
一、Task和任务编程
什么是Task(任务)?这个定义不好下也不太好精确理解。在计算机中,一个任务指的是一个或多个指令序列。在TBB中个人更倾向于任务是一种更抽象的逻辑处理的过程。任务做为一个并行环境中的基础工作单元,其采用了任务窃取的机制(task stealing)。它可以是一个函数,一个类或者一个Lambda表达式等。通过任务可以更高效的进行工作的分配和执行,以实现更好的并行处理。
在TBB中提供了一系列的相关的任务处理类和辅助函数,如tbb::task和task_arena等等。任务可以简单理解成一种与线程交互的处理机制,但是它只提供工作机制而不处理类似于线程底层的堆栈及上下文等的控制机制。
基于任务的编程就是从任务这层更抽象的机制入手进行编程,屏蔽掉线程的处理,而交由相关的任务框架来自动完成。可以理解为对线程的实际工作的抽象,形成普通人可以理解的现实工作方式,从而屏蔽了线程的整体管理过程。
二、Task和Thread
如果单纯从对文字的抽象理解来看,二者可能区别不大。但在实际的技术应用中,这二者的区别可还真不小。任务这个名字可能更抽象,它可以是一个指令也可能是一个进程或线程。可能在不同的语境下,有着不同的情况。反而线程描述更准确,更容易为开发人员明白。在TBB中一般鼓励开发者使用一些更高级的并行方式即如parallel_for、parallel_reduce等相关方式来处理任务,而不是直接对任务进行再处理。
TBB中提供了task_arena,由其来处理任务与纯种的亲和性。同时提供了tbb::task_group 和 tbb::structured_task_group 类,允许开发者不需要直接去继承Task来完成具体的任务。也就是说tbb::task是一个非常基础的概念,但只要掌握了它,就可以更好的理解上述的应用扩展类。
回到TBB中的任务与Thread的不同这个问题,如果有协程的概念,可以理解成一个类似的框架(当然他们有本质的不同,不过在外表的现象有些类似)。那么此时就可以比较清楚的明白任务和线程的不同:
1、抽象的层次不同,Task更抽象,也就意味着它更轻便,不需要维护太多的堆栈或上下文等资源开销
2、提高了负载均衡,让任务和线程进行匹配,而不是直接运行线程,特别是在NUMA多核CPU上
3、任务与线程的资源可由创建者在开发时指定,即将并行性与资源适配,而线程的资源一般是固定的
4、任务的创建更高效维护成本更低 ,线程创建和调试、销毁成本很高。
5、任务调试器的效率更高一些,可以更好的提供任务启动的安排,而线程的安排不可控(由OS控制)
三、TBB中任务的特点
TBB中的任务的特点主要有以下几个:
1、从设计角度抽象出任务
2、任务有自己的调试器来控制运行及与线程的工作
3、任务是一个很轻的工作单元
4、任务非常高效,可以关注到具体的任务内容
5、支持任务的窃取
四、任务的限制
在分析了任务的优势后,同样会有一个疑问,任务就没有缺点了么?不,它是有的。最典型的就是在阻塞型任务较多时,或者说大量的阻塞IO调用时,TBB的任务调度器就会导致低效的结果。所以在这种情况下,仍然推荐使用成熟的线程的方式来进行工作的处理。
五、总结
从TBB框架中的任务可以瞥见一种趋势,即在设计上对底层进一步封装,特别是对一些复杂的如线程、进程以及IO的操作。尽量屏蔽与硬件结合较紧密的部分功能,从而在更高的抽象上实现对目标的解耦。而解耦的最终目的,就是让整个框架的适应性更广泛,应用更灵活。
这也是做为开发者学习框架的一个重要的原因和目的。最后,学会了一定要学以致用。