线程模型
pthread:1:1,一个内核线程里只有一个用户线程,作为独立的调度单元可以被调度到多个cpu上
- 优点:多核扩展性好,起多个用户线程可以跑在多个cpu上
- 缺点:多个用户线程之间数据交互,互斥访问增加代码复杂度,即使用原子变量,当用户线程从cpu0切换到cpu1时,data要从l1 cache同步到其他cpu(锁住总线),所以起太多的pthread对于内核调度压力较大
协程(改进):n:1,n个用户线程跑在一个内核线程(一个cpu)
- 优点:无多线程竞争,写代码容易些,因为跑在一个cpu上,数据的locality会比较好,常见应用形式是nginx
- 劣势:很难扩展到多核,最多只能用一个cpu,多核只能用多进程,要用各种ipc;若一个用户线程阻塞会阻塞整个内核线程?
bthread(再改进):结合二者,m:n,每个bthread两种调度方式,既可以只在某一个内核线程里调度(locality不错),也可以在该bthread被卡住时,允许被偷到其他内核线程去运行,只要有空闲的worker在,就不会有bthread被阻塞,保证了多核在任意时刻只要有bthread被创建出来就可以去跑任务。
简单说下bthread是如何做调度的
bthread的机制核心,是work stealing,即线程(pthread)之间可以偷bthread来执行,一个bthread被卡住不会影响其他bthread。
- 因bthread api阻塞:会把当前的线程让给其他bthread执行
- 因pthread api阻塞:当前线程上待运行的bthread会被其他空闲的线程偷过去执行
context swtich:人工调boost库函数做调度,bthread_make_fcontext(保存栈和寄存器到buff)跳转当前程序运行的指针(tc指针)指向别处,再调bthread_jump_fcontext跳回来,实现用户态的上下文切换
context storage:contextualstack,三种size,small normal large,异常处理:如果溢出,用mprotect guarding page,只要程序对这个page做读写,程序会直接发coredump信号,而mprotect必须用mmap,貌似是45页对齐的缘故
scheduling:一个worker(pthread)会有很多bthread,每个worker通过一个taskgroup管理,taskgroup类中有_rq变量,维护一堆待执行的bthread,基本调度为先进先出顺序执行,1234,特殊情况下,比如1号线程block了,第2、3、4直至n号线程就都被阻塞了,那么空闲的worker就会去偷剩余的bthread栈去跑,防止一个bthread阻塞饿死整个pthread的情况。
简单理解,bthread执行用户指定的func过程种有以下两种情况
- func执行完毕:此时,先去当前在跑的taskgroup中的_rq中看是否有其他bth,如果有直接执行下一个,如果没有就去偷,偷不到最后会返回phtread的调度bth,也就是卡在wait_task处
- func中创建新bth或调用阻塞操作:立即进入新bth,原bth加入_rq尾部(这个被迫中断的bth不久后会被运行,但不一定在原来的pth上执行,可能被偷)