注:本文主要内容摘自笔者所著的《多核计算与程序设计》一书,略有修改,后续还会继续发布系列文章,如有需要,可以考虑将一下地址加入到您的浏览器收藏夹中: http://software.intel.com/zh-cn/blogs/category/multicore/ 。
从前面的 CNestTaskScheduler 的使用方法中可以发现,采用嵌套任务调度,可以很方便地将一个大区间拆分成更多的小区间,将各个拆分后的区间放入分布式队列中,然后各个线程再从分布式队列中取出相应的区间进行处理。
对于一个 for 循环来说,通常处理的都是一个区间,因此也可以使用任务调度的方式将其拆分成更小的区间进行并行化执行。下面就利用嵌套任务调度的方法来实现一个 Parall_For 功能。
1. 区间的描述: CRange 类
要实现对区间的分拆功能,使用一个类 CRange 来描述区间。在实际情况中,区间通常可以由两个整数表示区间开始和结束位置,也可以由两个迭代器变量来表示区间开始和结束位置。不过 CRange 是一个抽象接口类,它并不定义区间的开始和结束位置,区间的开始和结束位置由继承它的类去定义。 CRange 类用 C++ 定义如下:
class CRange {
protected:
CNestTaskScheduler * m_pTaskScheduler ;
public:
CRange (){};
CRange ( CNestTaskScheduler * p );
void SetTaskScheduler ( CNestTaskScheduler * p );
CNestTaskScheduler * GetTaskScheduler ();
virtual CRange * Split () = 0;
};
在 CRange 类中,最重要的一个接口是 Split() 接口,这个接口负责将一个区间拆分成两个区间,一个区间继续存放在原来的 CRange 对象中,另外一个区间存放在返回的 CRange 对象中。如果返回值为 NULL ,表明原来的区间不需要进行分拆。
CRange 类本身是一个接口类,用它主要是作为 Parallel_For() 函数的接口函数, Parallel_For() 函数的原型如下:
void Parallel_For(CRange *pRange );
由于 Split() 是一个纯虚函数,因此传给 Parallel_For() 函数的参数必须是一个继续了 CRange 类的派生类的实例。
2. 对 CRange 对象的处理过程
对于每个 CRange 对象, Parallel_For() 中需要对它进行处理,处理是通过任务调度器中的任务入口函数来处理的,处理过程如下图所示:
图 4 CRange 对象的处理过程
上面的处理过程可以用 C++ 代码实现如下:
/** CRange 的任务处理入口函数
@param void *pArg - 实际为一个 CRange 指针
@return unsigned int WINAPI - CAPI_FAILED 表示失败, CAPI_SUCCESS 表示成功
*/
unsigned int WINAPI RangeProcessTask(void *pArg)
{
CRange * pRange = ( CRange *) pArg ;
if ( pRange == NULL )
{
return CAPI_FAILED ;
}
CRange * pNewRange = pRange -> Split ();
if ( pNewRange == NULL )
{
delete pRange ;
return CAPI_SUCCESS ;
}
CNestTaskScheduler * pTaskSched = pRange -> GetTaskScheduler ();
pNewRange -> SetTaskScheduler ( pTaskSched );
TASK t1 , t2 ;
t1 . pArg = ( void *) pRange ;
t1 . func = RangeProcessTask ;
t2 . pArg = ( void *) pNewRange ;
t2 . func = RangeProcessTask ;
pTaskSched -> SpawnLocalTask ( t1 );
pTaskSched -> SpawnTask ( t2 );
return CAPI_SUCCESS ;
}
3. Parall_For 的处理流程
有了上面的 RangeProcessTask() 函数后,就可以用它作为嵌套任务调度器的任务入口函数,以实现 Parallel_For 功能。
在 Parallel_For() 中,主要实现对 CRange 对象的任务调度处理,需要将参数 pRange 作为根任务传入到任务调度器中进行处理。
Parallel_For() 的处理过程如下:
图 5 Parallel_For 的处理过程
Parallel_For() 的代码如下:
/** 并行 for 循的处理函数
将一个 CRange 进行并行处理
@param CRange *pRange - CRange 指针
@return void - 无
*/
void Parallel_For(CRange *pRange )
{
CNestTaskScheduler * p = new CNestTaskScheduler ;
TASK task ;
task . func = RangeProcessTask ;
task . pArg = pRange ;
pRange -> SetTaskScheduler ( p );
p -> BeginRootThread ( task );
delete p ;
}