协程(Coroutine)
协程类似线程,有自己的局部变量,和其他协程共享全局变量,有自己的栈(?),和线程最主要的不同是程序会同时有多个线程在跑,但是协程不是,在任何时候都只能有一个协程在跑,而且它在被显式要求时才会被挂起。协程有三个状态:suspended、running、dead。关于协程的操作被封装在coroutine
表中:
- 用create创建协程,它的参数是一个函数(一般为直接定义的匿名函数),返回一个thread类型
- resume开始或重启协程,参数为create出来的协程
- yield将协程挂起,等待下一次的resume
- 期间用status查看协程的状态,参数为create出来的协程
要注意的是resume会根据协程是否还能继续返回true或false,如果能继续执行则true后面还会返回yield中输入的参数。resume的参数会传递给用create创建的coroutine函数中,yield会返回其额外的参数。当协程结束时,其返回的值(如果有)也会变成resume的返回值。
local co = coroutine.create(function(i)
print(coroutine.yield("yield args1", i, 1))
print(i)
print(coroutine.yield("yield args2", i, 2), "in yield 2")
return "a", "b"
end)
print("1-----:", coroutine.resume(co, 4))
print("========")
print("2-----:", coroutine.resume(co, 10, 2, 3))
print("========")
print("3-----:", coroutine.resume(co, 5, 6, 7))
--[[ 输出
1-----: true yield args1 4 1
========
10 2 3
4
2-----: true yield args2 4 2
========
5 in yield 2
3-----: true a b
]]
协程最常见的一个应用便是解决生产者-消费者问题(producer-consumer problem),我们将发送端(producer)定义成一个协程,当接收端(consumer)需要信息的时候,便resume这个协程,它经过若干处理后返回要发送的值,并将协程挂起,等待下一次请求,这种设计只在我们需要的时候才去调用producer,因此也称为消费者驱动(consumer-driven),下面是一个简单的实现,来自lua官网:
function send(x)
coroutine.yield(x)
end
local producer = coroutine.create(
function()
while true do
local x = io.read()
send(x)
end
end)
function receive()
local status, value = coroutine.resume(producer)
return value
end
print(receive())
可以进一步对上面的结构进行扩展,增加一个filter处理数据,send和receive方法不变,将producer改为函数:
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end
function producer()
return coroutine.create(function()
while true do
local x = io.read()
send(x)
end
end)
end
function filter(prod)
return coroutine.create(function()
line = 1
while true do
local x = receive(prod)
x = string.format("%5d %s", line, x)
send(x)
line = line + 1
end
end)
end
function consumer(prod)
while true do
local x = receive(prod)
io.write(x, "\n")
end
end
p = producer()
f = filter(p)
consumer(f)
--consumer(filter(producer()))
协程还有一种作为迭代器的用法,就是在循环的时候每次通过yield来结束本次迭代,比如在一个递归函数中:
function func2(a, n)
if n == 0 then
-- xxx
coroutine.yield(xxx)
else
-- ...
func2(a, n - 1)
-- ...
end
end
function func1(a)
local n = table.getn(a)
local co = coroutine.create(function() func2(a, n) end)
return function() -- iterator
local code, res = coroutine.resume(co)
return res
end
end
-- 可以用wrap来代替上面的写法,它们是等价的
function func3(a)
local n = table.getn(a)
return coroutine.wrap(function() func2(a, n) end)
end
for p in func3{"a", "b", "c"} do
print(p) -- 这里的p就是func2里面返回的xxx
end