一、Task Scheduler
Task Scheduler,任务调度器。其实这个名词相对来说非常容易理解,毕竟从学习计算机开始,从操作系统的进程管理到内存管理,再到分布式中的各种任务管理,其中一个重要之处就在于任务的调度。现实世界中就是对工作的系统控制,而在计算机世界中就是对资源的系统控制。
在TBB中,Task Scheduler(任务调度器),是指一个loop模板的引擎。在实际的应用中,TBB文档推荐使用loop模板而非直接使用这个引擎。当然,如果实际的开发中遇到了无法将算法映射到给定的高阶模板(即loop 模板)时,只能使用任务调度器了。
上面的话不容易理解,简单解释一下,任务调度器是一个引擎,它用来支持一系列的公开的算法模板,这些算法模板其实就是循环的调用了这个引擎(也可以理解成引擎可以循环的处理这个模板)。比如TBB中常见的parallel_for、parallel_reduce等。
二、工作原理
所谓的任务调度器,在TBB中,任务调度器是通过类似于树的图表递归操作来进行。每个节点对应着一个任务对象,而每个任务又指向了其后驱(Successor)即继任节点。而每个对象都一个类似于智能指针的引用计数,通过它来计算继任节点以此为依赖的数量。TBB中的任务调度器在平衡深度和广度遍历时,倾向于使用最小内存处理需求并进行跨线程间的通信。
同样,线程在调用任务时,会按下面的规则进行处理:
- 得到上一个task的execute方法返回的task,如为空则继续调用其获得
- 在自身队列底部弹出一个task,如队列为空,则继续获取下一条进行判断
- 任选一个任务队列,从其顶部Steal一个task;如选择队列为空,则继续遍历其余队列,直到成功
如同内核,TBB也支持调度旁路(scheduler bypass),目的同样都是为了优化执行序列,提高效率。在这种情况下需要开发者直接指定下一个运行的任务。
TBB还支持任务再生,即不再使用任务的分配与再分配,直接创建一个任务进行执行。
另外还需要对持续传递进行一下简单的说明,其实这个也比较容易理解,就是如果让父任务在等待所有的子任务完成后再继续进行自己的任务,则有可能导致效率的下降。在某些情况下,可能不需要所有的子任务执行完成,父任务就处理就绪态了,此时,父任务如果再等待子任务,则等同于一种资源的浪费。解决方法就是父任务只负责子任务的创建而不等待其的完成。子任务不再是父任务的父子依赖关系,它只是父任务的一种持续任务(continuation task)。这个说明起来有点麻烦,理解应该还是比较容易的。这样的好处就在于,没有了依赖关系,则线程在执行完子任务务后就可以偷窃其它子任务进行工作,提高线程的利用效率。
三、注意点
在使用任务调度器时,需要注意以下几点:
- 尽量使用官方推荐的new方法分配task。避免创建局部或者文件作用域的task对象
- 请保证在运行任何任务前,它的兄弟任务都必须分配完成
- 采用持续传递、绕过调度器,以及任务再生等技术获得运行的最大性能
- 未标记为再执行的任务完成会自动销毁。因此,其继任者的引用计数会减少1,直到0时继任者会被自动产出
四、总结
任务调试器等于是TBB底层提供的一种基础控制流程的手段。它既是一个非常强大的应用方式,也是一种很复杂很难驾驭的任务调度方式,所以TBB的应用中,尽量还是多使用其上层封装好的相关并行算法和任务调度模板。这就和汽车一样,主流的情况下请采用自动档驾驶,但遇到一些特殊情况,可以使用手动方式进行驾驶。
万物相通,道理相同!