2021SC@SDUSC BRPC代码分析(九) —— bthread整体过程代码详解

2021SC@SDUSC


一、bthread整体流程

        上篇文章简单介绍了bthread常用到的一个基础类——resource pool,我们也可以从中体会到brpc框架在各个层面对高性能高效率的追求,这篇文章让我们正式进入bthread的整体流程,我将用文字和图片展示bthread正式运行时的流程,获得一个高屋建瓴的认识。
        
在这里插入图片描述
        全局单例TaskControl有多个TaskGroup,每个TaskGroup内有两个执行队列_rq(本地队列)和_remote_rq(远程队列),执行队列中存放着待执行的bthread,main_tid代表主流程。每个TaskGroup对应一个pthread,下面会结合代码分析。

二、代码分析

下面我们来看代码,首先看一下TaskGroup的初始化函数。根据传入的容量值创建两个队列rq和remote_rq,创建main_stack、TaskMeta和里面的main_tid。每个TaskGroup对应取得一个TaskMeta,TaskMeta存储关于它的各种描述值包括线程的调度可能会延迟、创建此任务的属性等多个值,注意到我们上篇文章讲到的资源池就被这里调用快速获得一个内存的分配。
在这里插入图片描述

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,最后运行完一切删除清空,不再细看。
在这里插入图片描述
woker_thread在生成g后,调用run_main_task,来看这个函数。每个worker会一直在while循环中,如果有可执行的bthread,wait_task会返回true(wait_task涉及一个重要的调度问题,我将在下篇文章着重讲解),否则将阻塞当前worker;wait_task的大致逻辑是首先去当前task_group的_remote_rq中pop,如果没有,则去其他的task_group的_rq和_remote_rq中pop。
在这里插入图片描述
在这里插入图片描述
当拿到可执行的tid后,调用TaskGroup::sched_to(&dummy, 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函数重点主要内容如下。首先执行remained,remained本身作为一个bthread在开始运行自己逻辑前需要做的一些工作,后面会看到。
在这里插入图片描述
然后执行该TaskMeta的函数,因为函数执行过程中该bthread可能会调度至其他worker,因此task_group可能发生改变,所以在后面重新对g进行设置,最后调用ending_sched。
在这里插入图片描述
ending_sched()会尝试获取一个可执行的bthread,如果没有的话,则下一个执行的则为_main_tid对应的TaskMeta;然后通过上面介绍过的sched_to(pg,next_meta)跳转到next_meta。
在这里插入图片描述
然后介绍下开始提到的_main_tid、_main_stack,其实从上面init可以看出,TaskGroup是一个pthread,在执行bthread时,会运行在该bthread的栈中,其他时刻都是运行在pthread栈中。但是brpc并没有为pthread重新分配一个栈,而是仅仅记录了pthread栈的位置,main_stack即为pthread栈,而main_tid则代表了这个pthread。这也是brpc高性能的一个表现,我们从多方面发现了这点。下面我们来看看代码,这部分是怎么被实现的。
在最初的init可以看到这段初始化。
在这里插入图片描述
STACK_TYPE_MAIN即为main_stack的类型,get_stack会调用StackFactory的get_stack,StackFactory是个模板类,get_stack会分配栈空间,然后针对STACK_TYPE_MAIN做了特化,此时不会分配栈空间,仅仅返回一个ContextualStack对象(具体内容在/bthread/stack_inl.h等相关部分)。
在这里插入图片描述

然后在切换到bthread执行的过程中,会调用jump_stack(cur_meta->stack, next_meta->stack),cur_meta此时为main_tid对应的taskmeta,next_meta为即将要执行的meta,因此main_tid的stack便指向了pthread栈。
在这里插入图片描述

看完底层的功能实现,接下来看下bthread提供的接口,以bthread_start_urgent和bthread_start_background为例,正如函数名所示,前者对新建的bthread以高优先级处理,后者以低优先级处理,首先看下bthread_start_urgent。
在这里插入图片描述
上面提到过,worker的线程tls_task_group为tls,普通pthread的tls_task_group为null,先以普通pthread看下整体流程;此时普通pthread会调用start_from_non_worker()。start_from_non_worker()会尝试获取TaskControl单例,如果没有则创建一个,并初始化好一定数量的taskgroup;然后选择一个TaskGroup,调用start_background<true>
在这里插入图片描述

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的逻辑前会先执行remained,start_foreground会抢占当前bthread的执行,因此通过remain将当前bthread重新push到rq中等待执行。
在这里插入图片描述
接口bthread_start_background对于普通pthread的情况和bthread_start_urgent一致,而对于bthread_worker则会调用start_background<false>,此时在新建TaskMeta后会调用ready_to_run,此时会将该bthread移入到rq中,而不是直接切换运行,即实现了低优先级。
在这里插入图片描述


总结

以上就是今天介绍的全部内容,本文进行了bthread整体运行调度流程部分详细源码分析,之后的博客会继续进行其他代码的分析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值