一个C/C++协程库的思考与实现之协程的跨线程负载均衡调度

https://github.com/DoasIsay/ToyCoroutine

其实一开始我并不想让ToyCoroutine支持多线程,多进程SO_REUSEPORT其实也挺好的,大家各跑各的互不影响,但在写代码时我又时刻考虑到多线程,毕竟,没有多线程间的同步协作那该是多么的无聊啊,,,

我在想当在多线程环境中使用协程库时,比如在一台8核的机器上,创建7个线程(调度器线程),每个线程负责一部分协程的调度执行,如何避免一些线程很忙,另一些线程却很空闲处于无协程可调度执行的状态?

把忙的线程中的协程分一部分到闲的线程中,在这个过程中要保证不能破坏协程运行的上下文环境,首先要从理论上分析它的可行性,如果理论上不可行,就不用再去瞎折腾了

这其实就是要协程能被跨线程调度,即一个协程可以在合适的时机(状态不为RUNNING)与当前调度器线程解绑,然后绑定到其它调度器线程,被任一个调度器线程调度运行

我所认为的协程就是一个函数的调用过程,从一个函数调用链的某个栈帧跳到另一个合适的函数调用链的某个栈帧就是协程的调度切换

  

如上图协程的上下文的保存与恢复,在一个线程中save协程的上下文,在另一个线程中restore它,这会导致什么问题?这与同一个线程中的协程上下文的save/restore有什么不同?简直一样,没有什么不同,,,

同一个进程内所有线程共享进程的虚拟地址空间,也就是一个线程是可以访问另一个线程栈上的局部变量而不会导致进程core掉,栈上的局部变量都可以访问,更何况堆中的一块内存?这里可以把协程看做一块内存,跨线程调度,即:把一个线程调用了一半的函数调用让另一个线程去继续调用,只是换了一个线程来读写这块内存而已,所以协程跨线程调度理论上应该是可行的,但还要验证,事实是确实可行

要实现协程的跨线程负载均衡调度还要解决几个小问题

问题1

协程是和线程绑定死的,一个协程只能被一个线程调度执行,我们要实现跨线程的协程调度,首先就要把corMap的__thread去掉,加个spinlocker让它它变成一个普通的全局共享的对象,同一个进程内的所有线程共享,其实corMap也没必要是线程局部变量,协程运行调度过程中并不会频繁的去访问它,如果确实频繁还可以分段锁

问题2

协程的cid在同一个线程内是唯一的,既然corMap在同一个进程中已经是全局共享的了,那么协程cid也要在同一个进程内唯一,网络IO协程cid同socket fd,在同一个进程内天生唯一,无网络IO协程id从10000000开始分配,每个线程中有一个主协程cid是0,主协程可以规定不能跨线程调度,否则又要想办法如何区分主协程,如在Coroutine类中增加一isMain成员变量去区分,因为主协程是我们正常退出任务的返回点,所以它一定不能被跨线程调度,也就没必要再去为它分配一个唯一的cid

问题3

如何把一个线程内的协程交给另一个线程去调度执行,我们把Queue类改造一下加个spinlocker然后创建一个全局的globalRunQueue,当一个线程中有太多协程要调度执行时就会拿出部分协程,丢到globalRunQueue中去分给其它线程,每当epollWait超时返回后就去globalRunQueue中poll一下,如果能拉到就push到本地的无锁runQueue

问题4

怎么判断我很忙,我处理不过来这么多协程的调度运行?我负载比较高?

  1. 如果线程1的epollWait返回100次都是因为有读写事件返回,而线程2返回100次都是因为超时,那么是不是可以简单的认为线程1是比线程2的负载高
  2. 如果我们实时采集线程的cpu使用率,设一阀值如超过此阀值就认为负载高,反之就认为负载低,如此甚妙
  3. 如果对所有放入runQueue的协程都增加入队时间,如果发现超时调度,那么就可认为此线程负载比较高

经过验证

方案1的检测不是很准确

方案3要选择一个合适的超时时间,其实方案3是一种比较稳的实现方式

但是我更中意方案2因为它更有意思,通过检测调度器线程的cpu使用率来进行负载均衡调度,当调度器线程的cpu使用率大于LOADBALANCE_FACTOR且进程cpu使用率小于(LOADBALANCE_FACTOR*调度器线程数),则进行负载均衡的relax分享本地的无锁runQueue 1/2的协程到全局共享队列globalRunQueue,当调度器线程的cpu使用率小于LOADBALANCE_FACTOR就进行负载均衡的stress从全局共享队列globalRunQueue获取一个协程到本地的无锁runQueue

我们只需要判断出那个线程比较忙,负载比较高就行了,并不需要判断那个线程比较闲而把协程调度到比较闲的线程中去执行,因为globalRunQueue中的协程是比较闲的线程主动去拉取执行的,闲的没事就去globalRunQueue拉一拉

第五个要解决的问题,每次要丢多少协程到globalRunQueue?这个问题哦,丢多少?算球了,就丢1/2吧,不行就再来个1/2,折半,折半,折一半又一半,一半不行再一半,二分查找难道不是分而冶之的思想?

第六个要解决的问题,如何避免过度频繁不必要的负载均衡调度?这将是一个在实践中持序优化改进的地方

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值