一个关于协同程序的经典示例是“生产者-消费者”问题。这其中涉及到两个函数,一个函数不断地产生值(比如从一个文件中读取值),另一个则不断地消费这些值(比如将这些值写到另一个文件)。通常,这两个函数大致是这样的:

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匹配起来。这是一个典型的“谁具有主循环(who-has-the-main-loop)”的问题。由于生产者和消费者都处于活动状态,它们个字具有一个朱迅欢,并且都将对方视为一个磕掉用的服务。
协同程序被称为是一种匹配生产者和消费者的理想工具,一对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

因此,生产者现在一定是一个协同程序:

producer = coroutine.create(
function ()
while true do
local x = io.read() -- 产生新的值
send(x) -- 发送给消费者
end
end
end)

在这种设计中,程序通过调用消费者来启动。当消费者需要一个新值时,它唤醒生产者。生产者返回一个新值后停止运行,并等待消费者的自此唤醒。将这种设计称为“消费者驱动(consumer-driven)”。
还可以扩展上述功能,实现“过滤器(filter)”。过滤器是一种位于生产者和消费者之间的处理功能,可用于对数据的一些变幻。
过滤器既是一个消费者也会死一个生产者,它唤醒一个生产者促使其产生新值,然后又将变换后的值传递给消费者。例如可以在前面代码中添加一个过滤器,在每行起始处插入一个行号。代码如下:

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 = ,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

接下来创建运行代码就非常简单了,只需将这些函数串联起来,然后启动消费者:

p = producer()
f = filter(p)
consumer(f)

或者,更简单地写为:

consumer(filter(producer()))

如果接触过UNIX的pipe(管道),那么本例的内容就不会很陌生。毕竟,协同程序也是一种(非抢先的)多线程。在pipe中没想任务都在各自独立的进程中运行,而在协同程序中每项任务都在各自独立的协同程序中运行。pipe在writer(消费者)与reader(生产者)之间提供一个缓冲器,因此它们的运行速度允许存在一定差异。值得注意的是,在pipe中进程间的切换代价恨到。而在协同程序中,切换代价则小得多,因此writer和reader可以彼此协作地运行。

----------

完整程序示例:

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 = ,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 p = producer()
f = filter(p)
consumer(f)
-- consumer(filter(producer()))

《Lua程序设计》9.2 管道(pipe)与过滤器(filter) 包含使用协同函数实现“生产者——消费者”问题的实例代码的更多相关文章

  1. Lua 程序设计 (Roberto,Ierusalimschy 著)

    1 开始 2 类型与值 3 表达式 4 语句 5 函数 6 深入函数 7 迭代器与泛型for 8 编译,执行与错误 9 协同程序(coroutine) 10 完整的示例 11 数据结构 12 数据文件 ...

  2. Python--线程队列(queue)、multiprocessing模块(进程对列Queue、管道(pipe)、进程池)、协程

    队列(queue) 队列只在多线程里有意义,是一种线程安全的数据结构. get与put方法 ''' 创建一个“队列”对象 import queue q = queue.Queue(maxsize = ...

  3. [ionic开源项目教程] - 第6讲 过滤器filter的使用

    过滤器filter的使用 1.回顾 再熟悉一下tab1.html的代码: <div class="list"> <a ng-repeat="item i ...

  4. AngularJS过滤器filter入门

    在开发中,经常会遇到这样的场景 如用户的性别分为“男”和“女”,在数据库中保存的值为1和0,用户在查看自己的性别时后端返回的值自然是1或0,前端要转换为“男”或“女”再显示出来: 如我要换个羽毛球拍, ...

  5. 关于Lua程序设计{读书笔记}

    1.lua中的标识符可以是由任意字母.数字和下划线构成的字符串,但不能以数字开头.2.lua将通常类似"_VALUE"的标识符作为保留标识符3.lua的保留字 and break ...

  6. 管道Pipe

    管道Pipe java.nio.channels包中含有一个名为Pipe(管道)的类.广义上讲,管道就是一个用来在两个实体之间单向传输数据的导管.管道的概念对于Unix(和类Unix)操作系统的用户来 ...

  7. Linux简单程序实例(GNU工具链,进程,线程,无名管道pipe,基于fd的文件操作,信号,scoket)

    一, GNU工具链简介: (1)编译代码步骤: 预处理 -> 编译 -> 汇编 -> 链接: 预处理:去掉注释,进行宏替换,头文件包含等工作: gcc -E test.c -o te ...

  8. 【IPC第二个进程间通信】管道Pipe

    IPC进程间通信+管道Pipe                IPC(Inter-Process Communication,进程间通信).         管道用于进程间共享数据,事实上质是共享内存 ...

  9. (转)Windows管道(Pipe)重定向stdout,stderr,stdin

    参考: http://qiusuoge.com/11496.html http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html st ...

随机推荐

  1. SSH-运行main函数,一直报空指针,调依赖注入配置的dao

    解决this.getHibernateTemplate()==null的问题 刚刚在整合SSH时碰到了这样一个问题: 当我用junit测试时不会报任何异常,数据也都能得到 但当我运行man函数,直接n ...

  2. 使用Maven创建Web应用程序项目

    用到的技术/工具: Maven 3.3.3 Eclipse 4.3 JDK 8 Spring 4.1.1.RELEASED Tomcat 7 Logback 1.0.13 1. 从Maven模板创建W ...

  3. asp.net 截屏

    public class HomeController : Controller { // // GET: /Home/ static System.Windows.Forms.WebBrowser ...

  4. php foreach 传值还是传引用

    From: http://my.oschina.net/guomingliang/blog/215457 php 中遍历一个array时可以使用for或foreach,foreach的语法为:fore ...

  5. linux中find命令

    1.使用name选项: 文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用. 可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来. 不管当前路径是什么,如果 ...

  6. Yii2 响应部分 response

    当应用完成处理一个请求后, 会生成一个yii\web\Response响应对象并发送给终端用户 响应对象包含的信息有HTTP状态码,HTTP头和主体内容等, 网页应用开发的最终目的本质上就是根据不同的 ...

  7. asp.net GridView实现多表头类 多行表头实现方法

    以上列表中运用的都是基本的东东: 1.多表头: 2.按值改变行颜色: 3.分页码 代码: AndyGridViewTHeaderHepler.cs //------------------------ ...

  8. CentOS6.5下安装iRedMail中需要解决的问题

    iRedMail是个专门用于Redhat/CentOS下的企业Mail服务集成安装软件包,本来只要有干净的系统就可以轻松安装,无奈国内网络状况和墙头众多,安装中也有很多问题需要解决,下面记录的都是我安 ...

  9. UNIX环境编程学习笔记(7)——文件I/O之文件访问权限与进程访问控制

    lienhua342014-09-02 1 文件的设置用户 ID位 和设置组 ID位 与进程相关联的 ID 如下表所示, 表 1: 与进程相关联的用户 ID 和组 ID 实际用户 ID 我们实际上是谁 ...

  10. 在Android中,px,dp,dip,sp的不同之处

           最近在学习Android开发,一直没有弄清楚px,dp,dip,sp的区别.今天正好有时间,就花时间研究了一下.     众所周知,Android厂商非常多,各种尺寸的Android手机 ...