两种任务调度方式(goroutine,coroutine)
coroutine-协同程序
(C#、Lua、Python支持coroutine特性)
与线程类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时与其他协同程序共享全局变量和其他大部分逻辑;
与线程区别:一个具有多个线程的程序,可以同时运行多个线程,而协同程序缺需要彼此协作的运行;在任一指定时刻,只有一个协同程序运行,并且正在运行的协同程序只有明确的被要求挂起时才会被挂起;协同程序有点类似多线程,在等待同一个线程锁的几个线程有点类似协作
生产者-消费者协作示例
local newProductor function productor() local i = 0 while true do i = i + 1 send(i) -- 将生产的物品发送给消费者 end end function consumer() while true do local i = receive() -- 从生产者那里得到物品 print(i) end end function receive() local status, value = coroutine.resume(newProductor) return value end function send(x) coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序 end -- 启动程序 newProductor = coroutine.create(productor) consumer()
goroutine-抢占式调度机制
(go支持goroutine特性)
调度器:
G-goroutine,每次go调用,都会创建一个G对象;
M-线程, 每次创建一个M的时候,都会有一个底层线程创建,所有G任务,最终还是在M上执行;
P-处理器,每一个运行的M,都必须绑定一个P,就像线程必须在一个CPU核上执行一样
调用过程
每次go调用的时候,都会: 1. 创建一个G对象,加入到本地队列或者全局队列 2. 如果还有空闲的P,则创建一个M 3. M会启动一个底层线程,循环执行能找到的G任务 4. G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找(一次性转移(全局G个数/P个数)个,再去其它P中找(一次性转移一半), 5. 以上的G任务执行是按照队列顺序(也就是go调用的顺序)执行的。(这个地方是不是觉得很奇怪??) 对于上面的第2-3步,创建一个M,其过程: 1. 先找到一个空闲的P,如果没有则直接返回,(哈哈,这个地方就保证了进程不会占用超过自己设定的cpu个数) 2. 调用系统api创建线程,不同的操作系统,调用不一样,其实就是和c语言创建过程是一致的,(windows用的是CreateThread,linux用的是clone系统调用),(*^__^*)嘻嘻…… 3. 然后创建的这个线程里面才是真正做事的,循环执行G任务 那就会有个问题,如果一个系统调用或者G任务执行太长,他就会一直占用这个线程,由于本地队列的G任务是顺序执行的,其它G任务就会阻塞了,怎样中止长任务的呢?(这个地方我找了好久~o(╯□╰)o) 这样滴,启动的时候,会专门创建一个线程sysmon,用来监控和管理,在内部是一个循环: 1. 记录所有P的G任务计数schedtick,(schedtick会在每执行一个G任务后递增) 2. 如果检查到 schedtick一直没有递增,说明这个P一直在执行同一个G任务,如果超过一定的时间(10ms),就在这个G任务的栈信息里面加一个标记 3. 然后这个G任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队列末尾,执行下一个G 4. O(∩_∩)O哈哈~,如果没有遇到非内联函数(有时候正常的小函数会被优化成内联函数)调用的话,那就惨了,会一直执行这个G任务,直到它自己结束;如果是个死循环,并且GOMAXPROCS=1的话,恭喜你,夯住了!亲测,的确如此 对于一个G任务,中断后的恢复过程: 1. 中断的时候将寄存器里的栈信息,保存到自己的G对象里面 2. 当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了。
区别
都可以将函数或者语句在独立的环境中运行;
goroutine支持并行执行,通过channel进行通信,coroutine顺序执行,通过让出和恢复操作来通信(yield、resume)
coroutine 的运行机制属于协作式任务处理,早期的操作系统要求每一个应用必须遵守操作系统的任务处理规则,应用程序在不需要使用 CPU 时,会主动交出 CPU 使用权。如果开发者无意间或者故意让应用程序长时间占用 CPU,操作系统也无能为力,表现出来的效果就是计算机很容易失去响应或者死机。
goroutine 属于抢占式任务处理,已经和现有的多线程和多进程任务处理非常类似。应用程序对 CPU 的控制最终还需要由操作系统来管理,操作系统如果发现一个应用程序长时间大量地占用 CPU,那么用户有权终止这个任务。
参考:
lua协同程序:https://www.runoob.com/lua/lua-coroutine.html
golang的goroutine调度机制:https://blog.csdn.net/liangzhiyang/article/details/52669851
goroutine和coroutine的区别: http://c.biancheng.net/view/96.html