那个协程占据主循环

简介: 那个协程占据主循环

有关协程的最经典示例之一就是生产者-消费者问题。在生产者-消费者问题中涉及两个函数,一个函数不断地产生值(比如,从一个文件中读取),另一个函数不断地消费这些值(比如,将值写入另一个文件中)。这两个函数可能形式如下:

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


为了简化这个示例,生产者和消费者都是无限循环的;不过,可以很容易地将其修改为没有数据需要处理时退出循环。这里的问题在于如何将 sendreceive 匹配起来,也就是“谁占据主循环”问题的经典实例。其中,生产者和消费者都处于活跃状态,他们各自具有自己的主循环,并且都将对方视为一个可调用的服务。对于这个特定的示例,可以很容易地修改其中一个函数的结构,展开它的循环使其成为一个被动代理。不过,在其他的真实场景下,这样的代码结构改动可能会很不容易。


由于成对的 resume-yield 可以颠倒调用者与被调用者之间的关系,因此协程提供了一种无需修改生产者和消费者的代码结构就能匹配他们执行顺序的理想工具。当一个协程调用函数 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(producer)


在这种设计中,程序通过调用消费者启动。当消费者需要新值时就唤醒生产者,生产者向消费者返回新值后挂起,直到消费者再次将其唤醒。因此,我们将这种设计称为消费者驱动式的设计。另一种方式则是使用生产者驱动式的设计,其中消费者是协程。虽然上述两种设计思路看上去是相反的,但实际上他们的整体思想相同。


我们可以使用过滤器来扩展上述设计。过滤器位于生产者和消费者之间,用于完成一些对数据进行某种变换的任务。过滤器既是一个消费者又是一个生产者,它通过唤醒一个生产者来获取新值,然后又将变换后的值传递给消费者。例如,我们可以在前面代码中添加一个过滤器以实现在每行的起始处插入行号:

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()))


代码的最后一行只是简单地创建出所需的各个组件,将这些组件连接在一起,然后启动消费者。


上述示例很容易联想到 POSIX 操作系统下的管道pipe )。使用管道时,每项任务运行在各自独立的进程中;而使用协程时,每项任务运行在各自独立的协程中。管道在写入者(生产者)和读取者(消费者)之间提供了一个缓冲区,因此他们的相对运行速度可以存在一定差异。由于进程间切换的开销最高,所以在一点在使用管道的场景下非常重要。在使用协程时,任务切换的开销则小得多(基本与函数调用相同),因此生产者和消费者可以手拉手以相同的速度运行。

目录
相关文章
|
Java
java有3个独立的线程,一个只会输出A,一个只会输出L,一个只会输出I。在三个线程同时启动的情况下,如何让它们按顺序打印ALIALI。
java有3个独立的线程,一个只会输出A,一个只会输出L,一个只会输出I。在三个线程同时启动的情况下,如何让它们按顺序打印ALIALI。
99 1
|
10天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
32 5
|
2月前
|
监控 Java API
|
7月前
|
并行计算 Java Linux
工作2年,有些人竟然还不懂进程、线程、协程之间的关系!
我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。
50 0
|
缓存 Go
控制goroutine 的并发执行数量
控制goroutine 的并发执行数量
145 0
|
Go 数据库
sync.Once-保证运行期间的某段代码只会执行一次
sync.Once-保证运行期间的某段代码只会执行一次
88 0
|
Python
Python编程 顺序执行与程序的主入口
Python编程 顺序执行与程序的主入口
135 0
三个线程按顺序打印ABC?十二种做法,深入多线程同步通信机制
大家好,我是老三,这篇文章分享一道非常不错的题目:三个线程按序打印ABC。 很多读者朋友应该都觉得这道题目不难,这次给大家带来十二种做法,一定有你没有见过的新姿势。
|
索引 Python
python 线程 ~~ ~~~为面试开辟VIP通道~~~~~测试、死锁、全局变量共享、守护主线程等。。。。。。(2)
python 线程 ~~ ~~~为面试开辟VIP通道~~~~~测试、死锁、全局变量共享、守护主线程等。。。。。。(2)
145 0
python 线程 ~~ ~~~为面试开辟VIP通道~~~~~测试、死锁、全局变量共享、守护主线程等。。。。。。(2)
|
存储 JSON 资源调度
python 线程 ~~ ~~~为面试开辟VIP通道~~~~~测试、死锁、全局变量共享、守护主线程等。。。。。。(1)
线程(英语:thread)是操作系统能够进行运算调度的最小单位。线程很重要,通过本篇文章可以让你们很好的了解线程的传参、线程执行规则、守护主线程、线程间共享全局变量、进程互斥锁、死锁进程怎么解决。希望对你们有所帮助。
235 0
python 线程 ~~ ~~~为面试开辟VIP通道~~~~~测试、死锁、全局变量共享、守护主线程等。。。。。。(1)

相关实验场景

更多