参考文章:
菜鸟教程
https://zhuanlan.zhihu.com/p/480357405
https://zhuanlan.zhihu.com/p/76249973
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同有非常强大的功能,但是用起来也很复杂。
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起(yield)的时候才会被挂起。
协同程序有点类似于同步的多线程,在等待一个线程锁的几个线程有点类似协同。
方法 | 描述 |
---|---|
coroutine.create() | 创建coroutine,返回coroutine对象,参数为一个匿名函数或函数,当和 resume 配合使用的时候就唤醒函数调用。 |
coroutine.resume() | 重启 coroutine,和 create 配合使用。 |
coroutine.yield() | 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果。 |
coroutine.status() | 查看 coroutine 的状态。 注:coroutine 的状态有三种:dead,suspended,running,具体什么时候有这样的状态请参考下面的程序。 |
coroutine.wrap() | 创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复。 |
coroutine.running() | 返回正在运行的 coroutine,一个 coroutine 就类似于一个线程,当使用running的时候,就是返回一个 corouting 的线程号。 如果是主协程,返回的结果后面会跟一个true,表示当前的调用coroutine.running()的区域位于主协程中,否则则为false。 |
TIPS:
上述提到的coroutine不仅仅指create出来的coroutine,同时包括默认的主coroutine。
主协程是指Lua的默认执行线,用coroutine.create创建的协程不是主协程。
协程的基础操作
co = coroutine.create(function()print("123")end
)coroutine.resume(co) --运行协程,打印123
新创建的协程并不会运行,而是处于挂起状态,可以通过coroutine.resume
让其运行。这是因为Lua提供的是非对称协程(asymmertric coroutine
),也就是说需要两个函数来控制协程的运行,一个用于挂起协程的执行,另一个用于恢复它的执行。
以下实例演示以上各个方法的用法:
co = coroutine.create(function(i)print(i);end
)coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- deadprint("----------")co = coroutine.wrap(function(i)print(i);end
)co(1)print("----------")co2 = coroutine.create(function()for i=1,10 doprint(i)if i == 3 or i == 2 thenprint(coroutine.status(co2)) --runningprint(coroutine.running()) --thread:XXXXXX false表示在协程中,true表示在主协程中endcoroutine.yield()endend
)coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3print(coroutine.status(co2)) -- suspended
print(coroutine.running())
输出结果:
coroutine.running就可以看出来,coroutine在底层实现就是一个线程。当create一个coroutine的时候就在一个新线程中注册一个事件。当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等待再次resume触发事件。
接下来分析一个更详细的实例:
function foo (a)print("foo 函数输出", a)return coroutine.yield(2 * a) -- 返回 2*a 的值
endco = coroutine.create(function (a , b)print("第一次协同程序执行输出", a, b) -- co-body 1 10local r = foo(a + 1)print("第二次协同程序执行输出", r)local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入print("第三次协同程序执行输出", r, s)return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
输出结果:
以上实例介绍如下:
从上述我们可以得出结论:resume和yield的配合强大之处在于resume处于主协程中,它将外部状态(数据)传入到子协程中的;而yield则将内部的状态(数据)返回到主协程中。
我们发现resume被调用后,有两种传递方式,一种是匿名函数的参数进行传递,一种是通过yield的返回值进行传递。
通过上述介绍,我们可以得出以下代码的输出结果的合理性:
co = coroutine.create(function(x)print(coroutine.yield())
end
)coroutine.resume(co,1) --不打印任何值
coroutine.resume(co,1,2) --打印 1 2
消费者不需要,则生产者就不生产。消费者一有消费需求,生产者立即生产。
local newProductorfunction Productor()local i = 0while true doi = i + 1send(i) -- 将生产的物品发送给消费者end
endfunction Consumer()while true do local i = recv() -- 从生产者那里得到物品print(i)end
endfunction recv()local status, value = coroutine.resume(newProductor)-- print(status)return value
endfunction send(x)coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序
endnewProductor = coroutine.create(Productor)
Consumer()
输出结果:
输出结果由于消费者一直有需求,生产者是一直在生产的。
上一篇:Java多线程案例之线程池