Skynet_协程

Skynet_协程

协程的基本概念

每个lua虚拟机中可以有多个协程协调工作,但虚拟机中永远只有一个协程在工作。

  1. 情况一:

    协程都会绑定一个 主体函数 。如果协程还没有运行过,直接调用 coroutine_resume(co,...) 就会导致协程开始运行,即开始执行主体函数,主体函数收到的参数就是上面的 ... 。调用者此时处于阻塞等待的状态。当主体函数执行完成的时候, coroutine_resume 也返回了,对于调用者来说, coroutine_resume 返回值就是主体函数的返回值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    local co = coroutine_create(
    function(money) --绑定了一个匿名主体函数

    -- dosomething

    return "绫罗绸缎" --ret的返回值
    end
    )
    local ok,ret = coroutine_resume(co,955) --当协程co执行的时候,当前调用者是阻塞的,即不能立即执行第10行
    print(ok,ret)

    ok 是第一个返回值, true 表示协程内部执行的时候没有报错

  2. 情况二:
    开始执行了一个主体函数,但在中途,主体函数让出了控制权,即调用 coroutine_yield(...) 挂起协程,此时coroutine_resume 会返回。返回值就是 ...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)
  3. 情况三:

    调用者需要协程再次工作,并调用协程。只需调用 coroutine_resume(co,...) ,就可让协程继续工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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_协程概念图

skynet的协程池具体工作原理

skynet本身是有协程池,如果需要一个协程,那么首先从协程池中去取;如果协程池中没有,那么就创建一个协程。 co_create(f) 就是获取一个协程。

注意: f 函数不是协程绑定的主体函数。 f 可以叫做当前协程的 任务函数 。 刚刚获取一个协程时,任务函数并不会马上执行。只有当协程被唤醒的时候,才会执行这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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) --f函数为任务函数
local co = tremove(coroutine_pool) --从协程池中取出一个协程
if co == nil then --一开始协程为空
co = coroutine_create(function(...) --匿名函数,此为主体函数
f(...) --首先执行任务函数f
while true do
-- coroutine exit

-- recycle co into pool
f = nil --把任务函数f清空
coroutine_pool[#coroutine_pool+1] = co --把协程回收
-- recv new main function f
f = coroutine_yield "SUSPEND" --挂起当前协程 当协程再次唤醒时 把传递进来的参数赋值给f
f(coroutine_yield()) --等于f(...),其中二次挂起(yield())是为了接收新f任务所需的参数
end
end)
else --取出协程池中的协程
-- pass the main function f to coroutine, and restore running thread
local running = running_thread --保存调用co_create所在的协程
coroutine_resume(co, f) --重新设置任务函数new_f ,coroutine_resume每次都会设置当前正在运行的协程
running_thread = running --恢复记录调用co_create所在的协程
end
return co
end

上面的代码看出, co_create 是对lua自带协程库包了一层。而调用 co_create( f ) 时指定的 f 函数 并不是这个协程真正绑定的主体函数。真正的绑定的主体函数是匿名函数 function(...) end

针对上面代码分析,举例调用:先假设协程池是空,当需要做一个任务时

  • 先获取一个协程 co = co_reate(f) ,此时会调用 coroutine_create 创建一个协程。(此时已经设置好 f 任务函数)

  • 调用 coroutine_resume (co,...) ,完成任务后协程会放回协程池,同时调用 coroutine_yield "SUSPEND" 把协程挂起。此时 coroutine_resume (co,...) 返回了,且返回值中就有 "SUSPEND"

    注意:协程虽然在逻辑上是回收了,但是它是挂起状态。

  • 外部通过 coroutine_resume(co, new_f) 唤醒协程并传入新任务函数 new_f ,并赋值给 f

  • 等待 new_f 的参数并执行新任务,外部通过 coroutine_resume(co, arg1, arg2, ...) 唤醒协程并传入参数coroutine_yield()返回这些参数,并传递给 f

  • f 执行其自身的逻辑。执行完毕后,循环再次开始,清空 f ,回收协程,挂起等待下一个新任务 coroutine_yield "SUSPEND"

关键点总结

  • 协程池:这套机制的核心目的是复用协程,避免重复创建的开销协程执行完一个任务后并不会销毁,而是通过 yield 挂起,放回池中,等待下一次被分配新任务。
  • 双重 yieldf(coroutine_yield())体现了其关键作用。它第一次挂起(yield "SUSPEND")是为了接收新任务函数,第二次挂起(yield())是为了接收该任务所需的参数
  • 灵活性:通过这种“函数+参数”分两次传递的方式,协程池可以处理各种不同的任务函数和其对应的参数,非常灵活。