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

bthread(三) bthread数据结构

阅读 : 91

TaskControl

base::atomic<size_t> _ngroup;
TaskGroup** _groups;                // TaskGroup对象指针的数组
base::atomic<int> _concurrency;     // TC启动时的默认起的pthread数量,可以理解为bthread并发度数量,静态开关配置为9
std::vector<pthread_t> _workers;    // pthread线程标识符的数组,表示创建了多少个pthread worker线程,每个pthread worker线程应拥有一个线程私有的TaskGroup对象。
ParkingLot _pl[PARKING_LOT_NUM];    // ParkingLot类型的数组。ParkingLot对象用于bthread任务的等待-通知

siganl_task():通知一部分parkinglot里的worker说有新bthread被创建出来啦,提醒worker去偷
steal_task():遍历所有worker去偷,防止某一个thread在某一个TaskGroup一直得不到run被饿死的情况

创建bthread只有两种情况(鸡生蛋蛋生鸡)

1、本身在bthread内创建bthread:因为当前bthread肯定跑在某个worker里,那就把新创建的bthread加到该worker的_rq里,这样locality貌似好一些

2、在pthread里创建bthread:TaskControl随机挑个worker(也就是TaskGroup),放到该worker的_remote_rq里(存放所有非worker创建的bthread),这个队列是个一读者多写者的队列,难做wait-free,用mutex保护

【调度优先级】

  1. 基本调度:bth在每个先进先出的rq队列中逐个执行
  2. 若本地(本worker)rq没有了则去本地_remote_rq里pop一个放到runq里运行
  3. 去其他worker的_rq里偷
  4. 去其他worker的_remote_rq里偷

【tips】worker拿到tid如果无阻塞就一定会执行完,有阻塞就从rq先拿掉放到队尾,再运行下个bthread,唤醒有别的机制。

【tips】如果一个worker内有一些pthread级别的阻塞,相当于这个worker就被阻塞了,那么其他worker会偷走该worker内被阻塞的bthread,保证整个系统可以顺利地跑在多核上。

TaskGroup

ContextualStack* _main_stack;       // TG单独持有的栈
bthread_t _main_tid;                // TG“调度线程”
WorkStealingQueue<bthread_t> _rq;   // 按序执行的bthread队列
RemoteTaskQueue _remote_rq;         // pthread下创建bthread会放入的队列
TaskMeta* _cur_meta;                // 当前正在执行的bthread的TaskMeta对象的地址

TaskGroup Interface(基本函数,用户不可见)

切线程相关

  • sched:封装sched_to,根据调度策略运行下一个要运行的bthread
  • sched_to(bth):切换到目标bth运行(底层用汇编实现,较为复杂的栈跳转逻辑,非常核心但并不是核心创新)

放队列相关

  • ready_to_run(bth):第1个动作把bth放到_rq,第2个动作调signal_task去唤醒一个任务去执行该bth
  • ready_to_run_remote:第1个动作加锁,第2个动作把bth放到_remote_rq,第3个动作调signal_task去唤醒一个任务去执行该bth
  • 补充:上述两个函数都有个参数signal,当新push一个bth时,会判断是否需要signal去提醒其他worker去偷,比如当前_rq很忙,我就希望别的赶紧来偷

【盗图】