2013-05-18

Schedule

前段时间用go把zeromq和leveldb粘合在一起,写了个玩具性质的queue(http://goo.gl/m3anm).

于是,一个纠结了很多次的问题又出现在了眼前.

站在API的角度来说,zeromq和levelddb都是可以异步的.
于是,使用上的一个问题就是,如果全部采用异步接口,那么就存在一种情况,所以调用都没ready的情况.

这时候,要么自己sleep一段时间,要么burn CPU.
就像没有epoll之前,如何使用poll一样.

所以这个问题的矛盾之处在于,异步或者说time sharing的目的为了用尽可能少的资源,做尽可能多的事情.
也就是减少浪费.
而,一旦出现某个时期,所有工作都是闲置状态的时候,却多数不得不做稍显浪费而又必须的loop/poll.

出现这个问题的本质原因,是站在上层角度来说,没有像系统一样的来自外界的唤醒重入机制.
像epoll,可以由外界的硬件通过信号触发处理例程的重入(尽管实现上,并不确切,但至少理论上是可以让CPU完全闲置,而不会有什么副作用的).

于是scheduler需要的是一种离开block状态的通知机制.

这个通过把schedule注入到signal handler里也是可以实现的.

问题是,不是所有的可能block的地方,都存在这种ready状态的回调.
因此,多数情况下的选择就是burn cpu了,所能做的优化只能是尽可能减少无谓的poll,同时尽可能降低调度带来的延迟.

毕竟在这种情况下,要么是poll早了poll多了,要么就是poll晚了.

目前go的sysmon的做法是采用2us的幂次增长,最多10ms的延迟调度,外加一个对网络的poll.
以期望尽可能地实时调度.

这算是一个很工程化的解决方法,能解决一部分问题,但不根本.
因为它依赖于这个间隔时间的调整.

事实上,许久之前自己也写过一个类似的东西(http://goo.gl/8ymEZ).
它的其中一个问题就是调度周期带设定.
尤其是面临一些繁忙程度比较浮动的情况.

而且,对于再上层应用的应用来说,就算go本身提供了这种机制,并且能够良好工作,自己写的时候也一样会遇到类似的情况.

就像开头说的,如果应用本身都是异步的,那么自身也需要有调度.
比如zmq_recv完了,发现没东西,那么可以直接一个goroutine出去.

在多数情况下,这个没什么问题.
但是如果其它goroutine也是这样的不ready状态,那么无非等价于一个空的for循环.

这里能想到的就是引入一些本身有block属性,同时能触发调度的.
也就是chan.

之前尝试过在for里加gosched,效果没什么改善.
因问题的本质是空闲造成的,gosched的结果不过去取出下一个空闲的goroutine而已.
做一些看似有用实则差不多的事情.

当然,如果都是纯粹的go code的话,也许不需要.
因为go"保证"了所有时刻都是在有效率地消耗CPU.
毕竟,任意go,或者chan等逻辑上的block操作都会触发调度.

但是遇到syscall以及cgo的话,就可能不太一样了.

cgo本质上都是syscall.
而syscall的问题在于,它会让出当前的P,然后调度器会为此多一个M去执行go code.
也就是,syscall的后果就是,多出一个worker线程.

虽然原则上来说,只有非常频繁的syscall才会导致线程数异常增长.
而且,即便如此,也可以通过lock或者buffered channel等限制syscall的并发数量.

但这个跟这里的问题关系不大.
cgo在这里的问题是,如果你的go code(main)跑完了,那么整个程序就结束了.

所以,这就要求go code里有一段"永远"block的调用.

如果是永远block的调用的话,对于默认单P/逻辑M/逻辑线程的go来说,就是以后都没有可用P了.
于是,即使cgo返回,由于没有逻辑上可用的P,程序也不会继续下去.

因此,它需要的是一个带yield的语义的调用.

这个yield,回到前面的话题,就是要考虑怎么尽可能不浪费而又实时.

于是,只要有着尽可能有效利用的想法的话,就逃避不开调度这个问题.
而多数情况下,都只能有很工程化的解.

没有评论:

发表评论

爽文

去看了好东西. 坦白说,多少是带着点挑刺的味道去的. 毕竟打着爱情神话和女性题材的气质,多多少少是热度为先了. 看完之后倒是有些新的想法. 某种程度上来说,现在的年轻人或者说声音就像小叶. 只要说点贴心的话就能哄好. 也是那种可以不用很努力了. 留在自己的舒适区避难所小圈子抱团就...