bjxagu 发表于 2013-2-5 02:39:59

用动态任务调度器实现Parallel_For

注:本文主要内容摘自笔者所著的《多核计算与程序设计》一书,略有修改,后续还会继续发布系列文章,如有需要,可以考虑将一下地址加入到您的浏览器收藏夹中: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()中需要对它进行处理,处理是通过任务调度器中的任务入口函数来处理的,处理过程如下图所示:
http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2009/05/taskscheduler4.gif
 
上面的处理过程可以用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()的处理过程如下:
<a href="http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2009/05/taskscheduler5.gif">http://software.intel.com/zh-cn/blogs/wordpress/wp-content/uploads/2009/05/taskscheduler5.gif
 
图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;
}
</p>
页: [1]
查看完整版本: 用动态任务调度器实现Parallel_For