bthread调度整体流程如下图所示
全局单例task_control有多个task_group,每个task_group内有两个执行队列_rq和_remote_rq,执行队列中存放着待执行的bthread。
TaskGroup对应一个pthread,初始化函数如下,创建rq和remote_rq,创建main_stack和main_tid;main_tid代表主流程,后面会具体讲main_stack和main_tid的作用
TaskControl是一个单例,下面是初始化的过程,主要逻辑即为创建_concurrency个bthread_worker线程,每个worker执行worker_thread函数
然后是worker_thread的逻辑,首先通过create_group创建一个TaskGroup g,添加到TaskControl中,设置tls_task_group为g,tls_task_group为tls,因此只有worker线程的tls_task_group为非null
然后执行run_main_task,每个worker会一直在while循环中,如果有可执行的bthread,wait_task会返回tid,否则将阻塞当前worker;wait_task的具体逻辑是首先去当前task_group的_remote_rq中pop,如果没有,则去其他的task_group的_rq和_remote_rq中pop,这里没有去当前task_group的_rq中pop的原因后面会提。
当拿到可执行的tid后,调用sched_to(tid)
sched_to(tid)中首先通过tid拿到该tid对应的taskmeta,taskmeta为一个bthread的meta信息,如执行函数,参数,local storage等;如果已经为该meta分配过栈,则调用sched_to(next_meta),该函数的主要逻辑为通过jump_stack(cur_meta->stack, next_meta->stack)跳转至next_meta;否则需分配栈,并设置该栈的执行入口为task_runner函数
task_runner核心如下图,首先执行remain函数,remain为一个bthread在开始运行自己逻辑前需要做的一些工作,后面会看到;然后执行该meta的函数,因为函数执行过程中该bth可能会调度至其他worker,因此task_group可能发生改变,所以287行重新对g进行设置;最后调用ending_sched
ending_sched会尝试获取一个可执行的bth,如果没有的话,则下一个执行的则为main_tid对应的meta;然后通过上述的sched_to(next_meta)跳转到next_meta。
上面提到wait_task时没有尝试去_rq中pop,可以在ending_sched中看到,只有当_rq为空才会切换回main_tid,因此在main_tid流程中,即run_main_task中,没必要去_rq中pop。
然后说下开始提到的main_tid/main_stack,task_group是一个pthread,在执行bthread时,会运行在该bthread栈中,其他时刻都是运行在pthread栈中。brpc并没有为pthread重新分配一个栈,而是仅仅记录了pthread栈的位置,main_stack即为pthread栈,而main_tid则代表了这个pthread。
下面来看下是如何实现这一过程的
在上面TaskGroup::init中,可以看到ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL);
STACK_TYPE_MAIN即为main_stack的类型,get_stack会调用StackFactory的get_stack,StackFactory是个模板类,get_stack会分配栈空间,然后针对STACK_TYPE_MAIN做了特化,此时不会分配栈空间,仅仅返回一个ContextualStack对象;然后在切换到bthread执行的过程中,会调用jump_stack(cur_meta->stack, next_meta->stack),
cur_meta此时为main_tid对应的taskmeta,next_meta为即将要执行的meta;由前面文章可知bthread_jump_fcontext执行时,会将当前各个寄存器push到当前栈中,即pthread栈,然后将esp赋值给(rdi),即from->context,因此main_tid的stack便指向了pthread栈
接下来看下bthread提供的接口,以bthread_start_urgent和bthread_start_background为例,如函数名所示,前者对新建的bthread以”高优先级”处理,后者以”低优先级”处理,后面会看到优先级的意思。首先看下bthread_start_urgent
由上可知,tls_task_group为tls,普通pthread的tls_task_group为null,先以普通pthread看下整体流程;此时普通pthread会调用start_from_non_worker
start_from_non_worker会尝试获取taskcontrol单例,如果没有则创建一个,并初始化好一定数量的taskgroup;然后选择一个taskgroup,调用start_background
REMOTE表示创建该bthread的线程是普通pthread还是bthread_worker,函数主要逻辑为创建taskmeta,然后调用ready_to_run_remote将该tid加入到taskgroup的remote_rq中
然后看下bthread worker调用bthread_start_urgent的过程,这种场景其实是在bthread中创建bthread,此时会调用start_foreground,然后创建taskmeta,并直接切换到这个新的bthread运行,即”高优先级”。
上图是start_foreground最后,这里会设置当前task_group的remain,上文提到在task_runner中,bthread在真正执行自己meta的逻辑前会先执行remain,start_foreground会抢占当前bthread的执行,因此通过remain将当前bthread重新push到rq中等待执行。
接口bthread_start_background对于普通pthread的情况和bthread_start_urgent一致;而对于bthread_worker则会调用start_background