新入门skynet系列视频b站网址 https://www.bilibili.com/video/BV19d4y1678X
skynet框架实现中用到了协程。特别是lua应用层在消息调度的时候。
- 基本概念
- skynet的协程框架
- skynet的协程池具体工作原理
协程
每个lua虚拟机可以有很多个协程。说个场景。一个车间为了赶制口罩,于是两班倒的开动机器工作。上白班的工人下班后,上晚班的工作继续干活。上晚班的下班后,上白班的又继续...
也就是上白班是一条线,上晚班是 一条线。他们的特点是不会同时工作,但是工作时共享相同的资源。比如说他们做口罩都是在9527号厂房,使用同样的十台机器。
这里的 一条线 就可以认为是一个协程。一个lua虚拟机中的多个协程协调工作。注意虚拟机中永远只有一个协程在工作
。
协程都会绑定一个主体函数
.如果协程还没有运行过,那么调用coroutine_resume(co,...)就会导致协程开始运行,即开始执行这个主体函数。既然主体函数开始执行,那么主体函数收到的参数就是上面的"..."。调用者此时处于阻塞等待的状态。当主体函数执行完成的时候,coroutine_resume也就返回了,对于调用者来说,coroutine_resume返回值就是主体函数的返回值。
local co = coroutine_create(
function(money) --绑定了一个匿名主体函数
-- dosomething
return "绫罗绸缎" --ret的返回值
end
)
local ok,ret = coroutine_resume(co,955) --当协程co执行的时候,当前调用者是阻塞的,即不能立即执行第10行
print(ok,ret)
ok是第一个返回值,true表示我们协程内部执行的时候没有报错
又假设,我们开始执行了一个主体函数,在中途,这个主体函数让出了控制权,即调用 coroutine_yield(ret), 那么coroutine_resume会返回。返回值就是ret。
local co = coroutine_create(
function(money) --绑定了一个匿名主体函数
-- dosomething 1
coroutine_yield("感觉快猝死了")
-- dosomething 2
return "绫罗绸缎"
end
)
local ok,ret = coroutine_resume(co,996) --此时ret的值是 "感觉快猝死了"
print(ok,ret)
上面的例子是协程说自己累了,于是让出了控制权,即 挂起协程
.但拼命干活本质是一种福报,调用者坚持协程继续开工。这个时候调用者调只需调用 coroutine_resume(co,50)
即可让协程继续工作。看下面代码。
local co = coroutine_create(
function(money) --绑定了一个匿名主体函数
-- dosomething 1
local tip = coroutine_yield("感觉快猝死了") --此时的tip就是 50
print("Much appreciated")
-- dosomething 2
return "绫罗绸缎"
end
)
local ok,ret = coroutine_resume(co,996) --此时ret的值是 "感觉快猝死了"
print(ok,ret)
--建议继续工作
ok,ret = coroutine_resume(co,50) --50是打车费
print(ok,ret) --此时ret的值是 "绫罗绸缎"
注意我们唤醒挂起的协程时,跟第一次执行主体函数一样,也是可以传递参数的。只是协程获取传递进来的参数是在 coroutine_yield函数的左边。
coroutine_yield的右边 是当前协程让出时传出去的值,
coroutine_yield的左边 是当前协程被唤醒时,外面传进来的值
skynet的协程
- 大致流程
- 具体流程
在skynet.lua中大量使用到了协程。
具体的工作流程是怎么样的?
skynet本身是有个协程池的,也就是说如果需要一个协程,那么首先从协程池中去取;如果协程池中没有,那么就创建一个协程。co_create( f ) 就是获取一个协程.注意 f函数不是协程绑定的主体函数。我把f叫做当前协程的任务函数
。刚刚获取一个协程时,任务函数并不会马上执行。只有当协程被唤醒的时候,才会执行这个函数。
local coroutine = coroutine --lua自带的协程库
local function coroutine_resume(co, ...) --唤醒协程
running_thread = co --唤醒协程时 记录当前正在执行的协程
return coroutine.resume(co, ...)
end
local coroutine_yield = coroutine.yield --挂起协程
local coroutine_create = coroutine.create -- 创建协程
local function co_create(f)
local co = tremove(coroutine_pool)--从协程池中取出一个协程
if co == nil then
co = coroutine_create(function(...)
f(...)
while true do
-- coroutine exit
-- recycle co into pool
f = nil
coroutine_pool[#coroutine_pool+1] = co
-- recv new main function f
f = coroutine_yield "SUSPEND"--挂起当前协程 当协程再次唤醒时 把传递进来的参数赋值给f
f(coroutine_yield())
end
end)
else
-- pass the main function f to coroutine, and restore running thread
local running = running_thread --保存调用co_create所在的协程
coroutine_resume(co, f) --重新设置任务函数 另外 coroutine_resume每次都会设置当前正在运行的协程
running_thread = running --恢复记录调用co_create所在的协程
end
return co
end
上面的代码看出,co_create是对lua自带协程库包了一层。而调用co_create( f )时指定的 f 函数 并不是这个协程真正绑定的主体函数。真正的绑定的主体函数是匿名函数
function(...) end
。
这里协程好像是一个人,当你需要完成一个任务时,你先找到一个人,然后给他安排这个任务,这个任务就是f。任务做完了,人就放到协程池里,其实就是挂起。当有其他任务需要人来做时,就去协程池叫一个人,并设置新任务f。
针对上面代码分析。先假设协程池是空的。当我们需要做一个任务时,我们一般
-
先获取一个协程 co = co_reate(f),这时候会调用 coroutine_create 创建一个协程。此时已经设置好 f 了
-
调用 coroutine_resume (co,...),这时候协程会运行起来,直到任务完成,就把自己放回协程池,同时调用 coroutine_yield "SUSPEND" 把自己挂起。这时候 coroutine_resume (co,...) 才算是返回了,且返回值中就有 "SUSPEND"。
需要注意的是:协程虽然在逻辑上是回收了,但是他是挂起状态。
coroutine_resume(co,...) 是唤醒指定协程,同时可以给这个协程传递参数。coroutine_resume调用的时候,被唤醒的协程开始执行代码,coroutine_resume此时不会立即返回,直到唤醒的协程挂起。coroutine_yield( ...) 是挂起当前协程。当我们挂起协程的时候,也就是coroutine_resume函数返回的时候。返回参数由coroutine_yield 指定。说多了怕搞混,实际上 coroutine_resume 是开始执行一个协程或者是唤醒一个挂起的协程 。 当你第一次开始执行一个协程,它会从绑定的主体函数处开始运行。 如果协程运行起来没有错误,
coroutine_resume
返回 true 加上绑定函数的所有返回值(绑定函数执行结束了)。如果是唤醒一个协程,则返回 true 加上传给coroutine_yield
的所有值。 如果有任何错误发生,resume
返回 false 加错误消息
假设我们又要做一个新任务,于是我们又调用co_create(f)去获取一个协程。此时就会执行下面的代码
local co = tremove(coroutine_pool)--从协程池中取出一个协程 此时会取出刚刚我们回收的协程
local running = running_thread --running_thread表示调用co_create的协程
coroutine_resume(co, f) --唤醒协程
running_thread = running
return co
此时拿到的协程,就是我们刚刚放回协程池的协程。还记得他的挂起位置码?是在 f = coroutine_yield "SUSPEND"。此时调用调用第四行 coroutine_resume(co, f),就会让协程继续运行。当然运行不会太久。看代码 第八行
while true do
-- coroutine exit
-- recycle co into pool
f = nil
coroutine_pool[#coroutine_pool+1] = co
-- recv new main function f
f = coroutine_yield "SUSPEND"--挂起当前协程 当协程再次唤醒时 把传递进来的参数赋值给f
f(coroutine_yield())
end
此时f被替换成新的任务函数 f了,然后 coroutine_yield() 又再次把自己挂起,此时 coroutine_resume(co, new_f)才算返回了。
注意此时协程挂起的位置,说明只要下次被唤醒,就可以执行f函数了。当f函数执行完,协程又会被回收到协程池,并挂起在 第八行。
当我们调用co_create获取到一个协程后,我们后面只要在合适的时机,再次调用coroutine_resume 即可开始执行我们的新任务了。
标签:co,resume,--,coroutine,yield,了解,协程 From: https://www.cnblogs.com/waittingforyou/p/17023069.html