菜鸟笔记
提升您的技术认知

brpc 笔记

bthread(一) 前言bthread(二) 线程模型及bthreadbthread(三) bthread数据结构bthread(四) bthread用户接口和代码执行路径bthread(五) 无锁队列rq的代码实现bthread(六) 小结brpc的精华bthread源码剖析brpc介绍、编译与使用brpc源码解析(一)—— rpc服务添加以及服务器启动主要过程brpc源码解析(二)—— brpc收到请求的处理过程brpc源码解析(三)—— 请求其他服务器以及往socket写数据的机制brpc源码解析(四)—— Bthread机制brpc源码解析(五)—— 基础类resource pool详解brpc源码解析(六)—— 基础类socket详解brpc源码解析(七)—— worker基于ParkingLot的bthread调度brpc源码解析(八)—— 基础类EventDispatcher详解brpc源码解析(九)—— 基础类WorkStealingQueue详解brpc源码解析(十)—— 核心组件bvar详解(1)简介和整体架构brpc源码解析(十一)—— Reducer类和Adder类解析brpc源码解析(十二)—— 核心组件bvar详解 AgentGroup类详解brpc源码解析(十三)—— 核心组件bvar详解(4)combiner详解brpc源码解析(十四)—— 核心组件bvar详解 sampler详解brpc源码解析(十五)—— bthread栈创建和切换详解brpc源码解析(十六)—— 作为client的连接建立和处理详解brpc源码解析(十七)—— bthread上的类futex同步组件butex详解brpc源码解析(十八)—— MPSC队列ExecutionQueue详解brpc源码解析(十九)—— 双buffer数据结构DoublyBufferedData详解brpc源码解析(二十)—— 用于访问下游的Channel类详解

bthread(二) 线程模型及bthread

阅读 : 1173

线程模型

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上执行,可能被偷)