一、什么是协同程序?
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
协同程序是一种计算机程序组件,它通过允许暂停和恢复执行,将子程序泛化以实现非抢占式的多任务处理。
二、协程 与 线程
线程是抢占式的,协程是非抢占式的。
一个多线程程序可以同时运行几个线程,而协程却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且在明确被要求挂起的时候才会挂起。
协程有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似。
线程数量通常不可以太多,而协程的数量可以非常多。如果线程的数量太多,那么大量线程的切换会影响性能。
协程的调度是应用代码自己实现的,而线程的调度是操作系统实现的。
三、基本语法
协程拥有四种状态,分别是:
挂起(suspended):当一个协程刚被创建时或遇到函数中的yield关键字时
运行(running):当协程运行时
正常(normal):当协程处于活跃状态,但没有被运行(这意味着程序在运行另一个协程,比如从协程A中唤醒协程B,此时A处于正常状态,因为当前运行的是协程B)。
死亡(dead):当协程运行完要执行的代码时或者在运行代码时发生了错误(error)。
四、基本操作
1. 创建协程
创建协程可以使用coroutine.create方法,传入的参数是匿名函数,即要执行的函数代码,返回的是一个新的协程。
co = coroutine.create( function() print('lua') end ) print(coroutine.status(co)) -- suspended
2. 执行协程
新创建的协程并不会运行,而是处于挂起状态,可以通过coroutine.resume
让其运行。
Lua提供的是非对称协程(asymmertric coroutine),需要两个函数来控制协程的运行,一个用于挂起协程的执行,另一个用于恢复它的执行。
co = coroutine.create( function() print(coroutine.status(co)) -- running print('lua') end ) coroutine.resume(co) -- lua print(coroutine.status(co)) -- dead(函数体运行结束,协程死亡)
3. 挂起协程
它强大的功能在于yield
函数,这个函数可以让一个运行中的协程挂起自己,然后通过resume函数让其恢复运行。
co = coroutine.create( function() coroutine.yield() -- 协程挂起 end ) coroutine.resume(co) print(coroutine.status(co)) -- suspended
4. 报错信息
co = coroutine.create( function() print('co') end ) coroutine.resume(co) print(coroutine.status(co)) -- dead coroutine.resume(co) -- 没任何输出
当协程运行完毕后再调用resume,并没有输出任何信息。
协程运行完所有代码后会进入死亡状态,resume一个死亡的协程不会出错吗?
因为函数resume和pcall一样,都是运行在保护模式中的。如果协程中出现错误,Lua语言并不会显示错误信息,而是将错误返回给函数resume。
co = coroutine.create( function() print('co') end ) coroutine.resume(co) print(coroutine.resume(co)) -- false cannot resume dead coroutine
5. wrap 创建协程
除了函数create
,还可以使用wrap
来创建一个协程,不过它返回的是一个函数,而不是协程。并且如果协程中出现错误,会中止协程并且将错误抛出
wrap = coroutine.wrap( function() for i = 1, 2 do print(i) coroutine.yield() end end ) wrap() -- 1, 然后yield wrap() -- 2, yield wrap() -- 此时status=suspended,所以这里调用才算结束协程所有代码块 wrap() -- cannot resume dead coroutine
6. 返回值
resume
的参数是协程中主函数的参数。resume
的返回值是协程主函数的返回值
co = coroutine.create( function(a, b) return a, b end ) print(coroutine.resume(co, 1, 2)) -- true 1 2
被挂起,那么resume就接收不到主函数的返回值,仅输出true。
co = coroutine.create( function(a, b) coroutine.yield() return a, b end ) print(coroutine.resume(co, 1, 2)) -- true print(coroutine.resume(co, 1, 2)) -- true 1 2 print(coroutine.resume(co, 1, 2)) -- false cannot resume dead coroutine
yield
返回值是再次唤醒协程的resume
中传进的参数。
第二次唤醒后,继续向下执行,return
,仍然返回是的先前的a, b = 1, 2
co = coroutine.create( function(a, b) print(coroutine.yield()) -- 3 4 return a, b end ) print(coroutine.resume(co, 1, 2)) -- true print(coroutine.resume(co, 3, 4)) -- true 1 2
运行结果:
true 3 4 true 1 2
resume返回值:yield的参数
co = coroutine.create( function(a, b) coroutine.yield(a + b, a * b) return a - b end ) print(coroutine.resume(co, 1, 9)) print(coroutine.resume(co, 'A', 'B'))
true 10 9 true -8
总结:
resume参数 = > =>=>(主函数参数、yield返回值)
resume返回值 < = <=<= (主函数返回值,yield参数)
五、demo
1. 协程运行流程
function foo(a) print('foo : ' .. ' a = ' .. a) return coroutine.yield(2 * a) end co = coroutine.create( function(a, b) print(a, b) local r = foo(a + 1) print('co : ' .. ' r = ' .. r) local r, s = coroutine.yield(a + b, a - b) print('co : ' .. ' r, s = ' .. r .. ', ' .. s) print('co : ' .. ' b = ' .. b) return b end ) print('main : ', coroutine.resume(co, 1, 2)) print(" ======================== ") print('main : ', coroutine.resume(co, "A", 'B')) print(" ======================== ") print('main : ', coroutine.resume(co, '!', '?')) print(" ======================== ") print('main : ', coroutine.resume(co, 'g', 'g')) print(" ======================== ")
运行截图如下:
如上分析,那么这里r=A
其实是return A, B
,但r
只能接住A
。
2. 生产-消费者问题
创建一个生产工厂,让它生产20件产品,每生产一件就把协程挂起,等待客户下一次提交需求的时候才重新resume唤醒
local newProductor function productor() local i = 0 while true do i = i + 1 send(i) -- 将生产的物品发送给消费者 end end function consumer() local i = receive() while i < 20 do print(i) i = receive() 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()
六、对称(symmetric) 与 非对称(asymmetric)
下图表示的就是对称协程,进入到该协程之后只能有一个操作就是yield,把cpu让回给调度器。
下图表示非对称协程,可以有两个操作,就是resume和yield,从哪里resume的,yield回哪。