第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些区别。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目


9.2 – 管道和过滤器

协程最具代表性的示例之一是生产者-消费者问题。假设我们有一个持续生成值(例如从文件中读取值)的函数,以及另一个持续使用这些值(例如将它们写入另一个文件)的函数。通常,这两个函数如下所示

    function producer ()
      while true do
        local x = io.read()     -- produce new value
        send(x)                 -- send to consumer
      end
    end
    
    function consumer ()
      while true do
        local x = receive()        -- receive from producer
        io.write(x, "\n")          -- consume new value
      end
    end
(在该实现中,生产者和消费者都无限期运行。当没有更多要处理的数据时,很容易更改它们以停止运行。)这里的问题是如何将 sendreceive 匹配。这是谁拥有主循环问题的典型情况。生产者和消费者都处于活动状态,都有自己的主循环,并且都假设对方是可调用服务。对于此特定示例,很容易更改其中一个函数的结构,展开其循环并使其成为被动代理。但是,在其他实际场景中,这种结构更改可能远非易事。

协程提供了一种匹配生产者和消费者的理想工具,因为恢复-生成对颠覆了调用者和被调用者之间的典型关系。当协程调用 yield 时,它不会进入新函数;相反,它返回一个挂起的调用(到 resume)。类似地,对 resume 的调用不会启动新函数,而是返回对 yield 的调用。此属性正是我们以一种使每个函数都充当主函数而另一个函数充当从函数的方式匹配 sendreceive 所需的属性。因此,receive 恢复生产者以便它可以生成新值;而 send 将新值生成回消费者

    function receive ()
      local status, value = coroutine.resume(producer)
      return value
    end
    
    function send (x)
      coroutine.yield(x)
    end
当然,生产者现在必须是一个协程
    producer = coroutine.create(
      function ()
        while true do
        local x = io.read()     -- produce new value
          send(x)
        end
      end)
在此设计中,程序开始调用消费者。当消费者需要一个项目时,它将恢复生产者,生产者运行直到它有一个项目要提供给消费者,然后停止,直到消费者再次重新启动它。因此,我们有了所谓的消费者驱动设计。

我们可以使用过滤器扩展此设计,过滤器是在生产者和消费者之间执行某种数据转换的任务。过滤器同时是消费者和生产者,因此它恢复生产者以获取新值,并将转换后的值生成给消费者。作为一个简单的示例,我们可以在以前的代码中添加一个过滤器,在每行的开头插入一个行号。完整的代码如下所示

    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()     -- produce new value
          send(x)
        end
      end)
    end
    
    function filter (prod)
      return coroutine.create(function ()
        local line = 1
        while true do
          local x = receive(prod)   -- get new value
          x = string.format("%5d %s", line, x)
          send(x)      -- send it to consumer
          line = line + 1
        end
      end)
    end
    
    function consumer (prod)
      while true do
        local x = receive(prod)   -- get new value
        io.write(x, "\n")          -- consume new value
      end
    end
最后一步只是创建它需要的组件,连接它们,并启动最终消费者
    p = producer()
    f = filter(p)
    consumer(f)
或者更好的是
    consumer(filter(producer()))

如果您在阅读上一个示例后想到了 Unix 管道,您并不孤单。毕竟,协程是一种(非抢占式)多线程。在管道中,每个任务都在一个单独的进程中运行,而使用协程,每个任务都在一个单独的协程中运行。管道在写入器(生产者)和读取器(消费者)之间提供了一个缓冲区,因此它们的相对速度有一些自由度。这在管道的上下文中很重要,因为在进程之间切换的成本很高。使用协程,在任务之间切换的成本要小得多(大约与函数调用的成本相同),因此写入器和读取器可以齐头并进。