多处理器/多核处理器的并行处理方法之——微线程

 

 上面有两篇文章介绍了两类比较典型且简单的并行方法,并且也简单地介绍了它们的性能以及优缺点。这里将再一种介绍方法:通过微线程来同步多个相互协作的并行任务。

我们来想像一下下面这个问题:在一个已知长度的巨大线性表(如一维数组)当中要搜索一个元素,并且其中的元素都是唯一的。我们现在所用的处理器有两个核,根据前面介绍的第二种并行方法,我们可以简单地让核A去搜索前一半元素,让核B去搜索后一半元素。当时我们将搜索操作作为单独的一个线程进行处理。但是像搜索这样的操作我们往往会在它结束后立即获得结果,然后接下去做其他事情。这时候,这个搜索操作将成为某个线程(任务)的一个串行(顺序操作)部分。而且这也显得比较自然。不过这也带来了一个问题:当一个核找到结果时如何让另一个核停止搜索动作而马上处理后面的事情。我这里想引入一个“微线程”的概念来更有效地处理这种情况。

我这里的“微线程”指的是:依附于线程,其栈空间为其所依附的线程的栈空间的子区间,并且微线程的操作作为线程的作业流的一部分,只有当微线程的操作完成后,才执行线程的其余部分。

可以用以下模型表示:

int ThreadA_Handler(void)
{
    Initialize();

    while(do_event(event_id1))
    {
        void* mem = AllocMemPool(MEM_SIZE_1024B);
        Initialize_Resource(mem);

        // Create micro-thread
        int elem = StartMicroThread(
                         /*thread id*/task_id_threadA,
                         /*micro-thread handler*/&do_handlerMA,
                         /*the argument for the handler*/mem,
                         );

        // Process the result
        Output(elem);
    }
    return THREAD_SUCCESS;
}


 上述代码中,StartMicroThread()函数就是用来创建微线程的接口。它记录下当前线程的ID,这个将用于后面将要讲述的中断处理中的微线程分派处理;然后将微线程处理函数传递进去;再传入微线程的输入参数。

下面将描述一下StartMicroThread()可能的样子:

void* StartMicroThread(TASK_ID task_id, /*input*/
                       void*(*pMicroThreadHandler)(void*),/*input*/
                       void* pParam,  /*input*/ 
                      )
{
    unsigned int context = sys_fetch_ret_address();
    RegisterMicroThreadHandler(task_id, context);
    void* ret = (*pMicroThreadHandler)(pParam);
    return ret;
}

上述代码中,sys_fetch_ret_address()系统函数表示获取当前函数所要返回的地址,这样就可以利用外部事件直接取消微线程的操作而转到StartMicroThread()的函数调用的下一条指令地址,当然这里可以加上一点处理来回收被使用的栈空间。可以在StartMicroThread()函数调用前后增加上下文的保护与恢复(这里的上下文保护与恢复无非就是对栈指针和帧指针(在x86中称为基指针BP)的保护与恢复)。

RegisterMicroThreadHandler()用来注册线程ID以及返回地址。当一个核通知另一个核动作已完成,使得另一个核立即结束微线程操作时,在中断处理中,分派器将判断当前活动线程是否为已注册的线程,若是,则将中断返回地址设为context值;否则,为该线程设置标志位,并且将context值传递给它,使得当该线程被再次调度时能立即结束微线程操作。因此,这个微线程概念也基于调度预处理机制,这个机制在目前很多主流操作系统中并没有使用。

第三条语句就是调用用户自定义的微线程处理函数,然后将结果返回。这里,往往可能将结果返回到某个共享存储区的变量中,因为很有可能在微线程操作没有完全操作完成时被强制退出,这时,返回值是不定的。所以这里设置返回值可能并不是一个好主意。

下面将利用宏函数来加入对栈上下文的保护与恢复:

#define BEGIN_MICRO_THREAD(task_id, handler, param) {  /
    SAVE_STACK_CONTEXT();    /
    StartMicroThread(task_id, handler, param);    /
    RESTORE_STACK_CONTEXT();    /
}

对于SAVE_STACK_CONTEXT()和RESTORE_STACK_CONTEXT()可以根据具体系统来实现。当然,使用内嵌汇编的形式也完全没有问题。

然后,我们有时可能会在微线程操作中申请了存储资源、锁或其它等资源。在这种情况下我们也可以利用一些手段来处理由于被强迫终止操作而没有被释放的资源。这个问题与线程中资源申请及释放的操作差不多,可以进行参考。像C++中的try-catch机制也值得参考。

最后还有一个问题不能被忽略。也就是退出微线程处理函数后应当注销所注册的上下文。我们可以再定义一个宏函数——

#define END_MICRO_THREAD(task_id) {    /
    UnregisterMicroThreadHandler(task_id);    /
    // You may add some resource releasing function calls here    /
}

这样在使用时,两个宏函数正好成对使用。

下面我们来讨论一下微线程处理函数里面操作的一些更具体的问题。

还是以搜索操作为例。在这个机制下我们基本上需要确定获得结果的微线程所附属的线程所在的核。就这点而言,微线程机制更适合于嵌入式系统。而另一个微线程作为计算的辅助操作。一般,总是辅助操作向获得结果线程所处的核发送中断请求信号,当它完成操作并且获得结果的微线程没有完成操作时。而获得结果的微线程当它完成操作后,但没有获得操作结果时(比如没有所搜到指定数据),那么就得等待另一个核的操作完成。因此,这个微线程只有当获得合适的结果时才会真正结束其操作,然后线程继续执行。而这也是为什么注册与注销上下文的函数只需要一个线程ID而不需要其它标志的原因。

作为辅助计算的微线程可以被调度到与获得结果的微线程所处的同一个核中。在这种情况下,效率也不会被降低,尤其是在另一个核总是忙于处理其它事务而长时间无法调度到这个搜索微线程所属的线程的时候。

最后,对于微线程处理函数的实现也可以参考:http://download.csdn.net/source/297300

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值