相互恢复的协程

时间:2019-07-17 12:49:19

标签: asynchronous lua coroutine

对于这个问题的长度,我事先表示歉意。我试图使其尽可能简洁,但这只是一个相当复杂的野兽。


在Ierusalimschy的 Lua编程(第4版)的第24章中,作者展示了任何异步I / O库的玩具实现(“丑陋”),例如这样的 1

-- filename: async.lua

-- Based (with several modifications) on Listing 24.3 (p. 246) of *Programming
-- in Lua*, 4th edition.

local async = {}
local queue = {}

local function enqueue (command) table.insert(queue, command) end

function async.readline (stream, callback)
  enqueue(function () callback(stream:read()) end)
end

function async.writeline (stream, line, callback)
  enqueue(function () callback(stream:write(line)) end)
end

function async.stop () enqueue("stop") end

function async.runloop ()
  while true do
    local next_command = table.remove(queue, 1)
    if next_command == "stop" then break end
    next_command()
  end
end

return async

作者使用这个玩具库来说明协程的一些应用,例如下面显示的用于在异步库之上运行“同步代码”的方案 2

-- Based (with several modifications) on Listing 24.5 (p. 248) of *Programming
-- in Lua*, 4th edition.

local async = require "async"

function run (synchronous_code)
  local co = coroutine.create(function ()
    synchronous_code()
    async.stop()
  end)
  local wrapper = function ()
    local status, result = assert(coroutine.resume(co))
    return result
  end
  wrapper()
  async.runloop()
end

function getline (stream)
  local co = coroutine.running()
  local callback = function (line) assert(coroutine.resume(co, line)) end
  async.readline(stream, callback)
  local line = coroutine.yield()
  return line
end

function putline (stream, line)
  local co = coroutine.running()
  local callback = function () assert(coroutine.resume(co)) end
  async.writeline(stream, line, callback)
  coroutine.yield()
end

作者使用此技术实现了一个功能,该功能以从stdin读取的行以相反的顺序打印到stdout:

function synchronous_code ()
  local lines = {}
  local input = io.input()
  local output = io.output()

  while true do
    local line = getline(input)
    if not line then break end
    table.insert(lines, line)
  end

  for i = #lines, 1, -1 do putline(output, lines[i] .. "\n") end

end

run(synchronous_code)

一般的想法是run函数创建一个协程,该协程将自己(通过getlineputline创建的回调)“注册”到异步库的主循环中。每当异步库的主循环执行这些回调之一时,它就会恢复协程,协程可以做更多的工作,包括在主循环中注册下一个回调。

run函数通过调用wrapper函数使球滚动,而该函数又“恢复”(实际上是启动)协程。协程然后运行,直到遇到第一个yield语句为止,在本示例中,该语句在getlinegetline已在async库的队列中注册了回调之后发生。然后wrapper函数重新获得控制权并返回。最后,run调用async.runloopasync.runloop开始处理其队列时,将恢复协程,然后我们离开。 “同步代码”(在协程中运行)一直持续到下一个getlineputline产生(注册回调后),并且async的主循环再次接管为止。

到目前为止,一切都很好。但随后,在练习24.4(p。249)中,作者问:

  

练习24.4::为基于协程的库编写行迭代器(清单24.5),以便您可以使用 for 循环读取文件。

(“清单24.5”是指上面第二个代码段中的代码,其中定义了rungetlineputline。)

我完全被这个困扰。在上面的示例中,协程通过将它们读取的行写到stdout来“交付”它可以单独完成的所有工作。相比之下,练习24.4要求的迭代器必须将其行传递给进行迭代的不同协程。

我能想象到的唯一方法是,两个协程可以互相恢复。那有可能吗?我无法构造一个简单的示例,希望看到能做到这一点的代码 3

另外,在我看来,要使其完全起作用,就需要使用一种write方法(以便可以将其传递给putline)来实现最终负责的对象。用于将行(以某种方式)传递到迭代器的协程。


1 我已经更改了一些肤浅的细节,例如变量的名称,缩进等。整体结构和功能未更改。

2 再次,我更改了一些无关紧要的细节,以使代码更容易理解。

3 值得注意的是,本章剩下的两个练习(24.5和24.6)都是关于实现涉及多个并发协程的系统的。因此,不难想象练习24.4也是关于两个协程互相交谈的。

1 个答案:

答案 0 :(得分:1)

我相信您完全没有考虑这一练习。以我的理解,您只打算为迭代器编写一种同步样式,该迭代器在提供给run函数的同步代码中运行。以第三个代码块为基础:

function for_file(file)
  return function(file)
    return getline(file)
  end, file, nil
end

function synchronous_code ()
  local lines = {}
  local input = io.input()
  local output = io.output()

  for line in for_line(input) do
    table.insert(lines, line)
  end

  for i = #lines, 1, -1 do putline(output, lines[i] .. "\n") end

end

run(synchronous_code)

正如您所看到的那样,您真的不需要完全了解协程,这样就可以了,这就是库的重点。