golang的协程调度
众所周知,golang作为协程调度模型,是非抢占式而是自主放弃式的。
我的理解是,当一个协程进行IO的阻塞操作时,就会让出CPU,让调度程序来调度其他协程来进行操作
调度程序并不会因为你的实际调用时间过长就干掉你,如果你觉得自己调用时间太长,可以用runtime库的Gosched()让出CPU
但实际的测试(基于1.7版本)和之前的理解有差距,测试过程是递进的,可以直接跳过看结论
测试
协程是否独占直到自己运行完毕版本1
1 | package main |
这段代码执行两个协程,但是用GOMAXPROCS限定了只用单核
预期是test1运行完输出,然后执行test2再运行完输出
但实际结果是
1 | test1 1000000000 1000000000 2017-07-24 09:51:41.577265937 +0800 CST |
然后我就误以为是在轮流执行
协程是否独占到运行完毕版本2
随后我测试了这一段代码
1 | package main |
输出
1 | test2 1000000000 |
如果按照测试代码1的结论,两边轮流执行直到结束,那应该输出的是test2, 0
然而现在并不是的,test1执行完了才执行了test2
于是我只能猜测fmt.Println函数会造成协程挂起,隐式的调用了Gosched()
这么一想,似乎就能说得通了,重看测试代码1.1
test1执行循环结束到fmt的时候挂起了,随后test2执行循环结束到fmt的时候又挂起了
此时i,j都是1000000000,然后依次调用test1的fmt和test2的fmt,输出相同的时间和i,j
但是新的问题又来了,时间为什么会相同呢?调用fmt会挂起,通过测试代码1.2应该能确认了
但是对于函数调用来说,参数会比函数先运行,因此测试代码1.1的两个函数的time.Now()记录的时间值,不应该是一样的
难道。。time.Now()函数也会引起挂起??
time.Now()函数是否会引起挂起
这一次的测试,在1.2的函数test1循环体中加入time.Now()
1 | func test1() { |
运行结果
1 | test2 493747 |
果然切换了,因为这里的i是493747(为什么不是0?稍后讨论),并且程序执行了很久才退出
这就说明test1执行了一半去执行了test2
这就很奇怪了,如果说fmt引起和标准输出的交互,从而导致切换也就算了
time.Now()凭什么切,难道申请内存就会切换??
申请内存是否会切换
这一次测试,在1.2的函数test1循环体中加入了申请内存
1 | func test1() { |
输出
1 | test2 957470 |
果然这一次也切换了,验证了申请内存也会导致切换
但是切换的i不是1,也就是说并不是每一次调用都会切换。而是按照时间来的。
单协程运行一段时间后,如果调用申请内存操作,就会被调度程序切换协程
测试调度程序切换时间
1 | var t time.Time |
输出基本是10ms以上波动一点点
总结
对一段纯计算的代码,调度程序并不会因为你的实际调用时间过长就干掉你,如果你觉得自己调用时间太长,可以用runtime库的Gosched()让出CPU
(基于1.7版本)你在计算的代码中申请了内存,那么如果已经运行了超过10ms,就可能被调度程序切换走,这也是为了防止饥饿吧