哪个协程占据主循环
协程的一个经典问题就是生产者-消费者问题。在生产者-消费者问题中涉及两个函数,一个函数不断地产生值(比如,从一个文件中读取),另一个函数不断地消费这些值(比如,将值写入另一个文件中)。
function producer ()
while true do
local x = io.read()
send(x)
end
end
function consumer()
while true do
local x = receive()
io.write(x, "\n")
end
end
我们的问题在于要如何将send和receive匹配起来,也就是“谁占据主循环”问题的典型实例。其中,生产者和消费者都处于活跃状态,它们各自拥有自己的主循环,并且都将对方视为一个可调用的服务。
由于成对的resume-yield可以颠倒调用者与被调用者之间的关系,因此协程提供了一种无需修改生产者和消费者的代码结构就能匹配它们执行顺序的理想工具。 当一个协程调用yield函数时,它不是进入了一个新的函数,而是返回一个挂起的调用(调用的是函数resume)。同样地,对函数resume的调用也不会启动一个新的函数,而是返回一个对函数yield的调用。这种特性恰好可以用于匹配send和receive,使得双方都认为自己是主动方而对方是被动方。因此,receive唤醒生产者的执行使其能产生一个新值,然后send则让出执行权,将生成的值传递给消费者:
function receive ()
local status, value = coroutine.resume(producer)
return value
end
function send (x)
coroutine.yield(x)
end
在这种设计中,程序通过调用消费者启动。每当消费者需要新值时就调用唤醒生产者,生产者向消费者返回新值后挂起,直到消费者再次将其唤醒。因此,我们将这种设计成为消费者驱动(consumer-driven)式的设计。另一个方式则是使用生产者驱动的设计。虽然两种设计思路看上去截然相反,但实际上它们的整体思想相同。
我们可以通过过滤器来扩展上述设计。过滤器位于生产者和消费者之间,用于完成一些对数据进行某种变换的任务。过滤器即是一个消费者又是一个生产者,它通过唤醒一个生产者来获得新值,然后又将变换后的值传递给消费者。
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end
function send (x)
coroutine.yield(x)
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 ()
for line = 1,math.huge do
local x = receive(prod)
x = string.format('%5d %s", line, x)
send(x)
end
end)
end
function consumer (prod)
while true do
local x = receive(prod)
io.write(x,"\n")
end
end
consumer(filter(producer))