两种任务调度方式(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

 

posted on 2019-12-17 15:42  wangsong412  阅读(1062)  评论(0编辑  收藏  举报