taskcontrol
base::atomic _ngroup;
taskgroup** _groups; // taskgroup对象指针的数组
base::atomic _concurrency; // tc启动时的默认起的pthread数量,可以理解为bthread并发度数量,静态开关配置为9
std::vector _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保护
【调度优先级】
- 基本调度:bth在每个先进先出的rq队列中逐个执行
- 若本地(本worker)rq没有了则去本地_remote_rq里pop一个放到runq里运行
- 去其他worker的_rq里偷
- 去其他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 _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很忙,我就希望别的赶紧来偷
【盗图】