当前位置: 首页 > news >正文

Lua--协程

一、协程的创建和状态

1.1创建协程:coroutine.create

Lua中所有协程相关的函数都封装在coroutine表中,coroutine.create是创建协程的入口,它接收一个函数作为参数,返回一个thread类型的值,代表创建的协程。

--创建一个简单的协程
co = coroutine.create(function () print("hello world") end)
print(co)  ->打印thread的地址

值得注意的是,创建协程不会处于运行状态,而是先到挂起状态——这是协程的初始状态,也是后续可恢复执行的前提。

1.2查看协程状态:coroutine.status

协程有四种状态:

挂起(suspended):初始状态,或者由yield挂起后的状态,可通过resmue恢复;

运行(running):协程正在执行中;

死亡(dead):协程主体函数执行完毕,或者因为错误终止,无法再次恢复;

正常(noramal):协程A唤醒协程B后,A所处的状态(非挂起也非运行)。

通过coroutine.status(协程对象)可以查看当前状态

co = coroutine.create(...)
print(coroutine.status(co)) ->suspend(初始状态)
coroutine.resume(co)
print(coroutine.status(co)) ->dead(运行完成)

1.3挂起协程:coroutine.yield

co = coroutine.create(function ()for i=1,3 doprint("co",i)coroutine.yield() --关键:到此处挂起协程end
end)
print(coroutine.status(co))

由上述实例,第一次调用resume时的执行流程

(1)coroutine.resume(co)启动协程,执行循环的第一迭代,打印

(2)coroutine.yield() 协程挂起,执行权返回给resume

(3)此处协程仍属于挂起状态,可以通过coroutine.stutas()查看。

yeild的作用不仅仅是“暂停”,更是“状态保存”——协程挂起时,局部变量的值会被保留,下次resum时从yield后面直接执行。

1.4协程状态流转

二、resume与yeild的协同,数据交换

2.1yeild的参数:成为resume的返回值

当协程通过yield(val1,val2,.....)挂起时,val1,val2,....或作为coroutine.resume的后续返回值(在true之后)

co = coroitine.create(function (a,b)coroutine.yield(a + b,a - b)
end)--resume返回true,以及yield的参数30、10
print(coroutine.resume(co,30,10)) ->true   30   10

2.2resume的后续参数:成为yield的返回值

当再次调用resume恢复挂起状态的协程时,传递给resum的额外额外参数,会成为yield调用的返回值。

co = coroutine.create(function ()-- yield的返回值来自下一次resume的参数local x, y = coroutine.yield()print("co", x, y)
end)
coroutine.resume(co)  -- 第一次resume:启动协程,执行到yield挂起
coroutine.resume(co, 4, 5)  --> co 4 5 (4、5成为yield的返回值)

2.3协程正常和错误的场景

resume在保护模式下运行,这意味着,即使协程执行中发生了错误,Lua也不会直接崩溃,而是将错误信息通过resume的返回值传递出来。这一特征让我们能够优雅地处理协程错误,也是resume返回值设计的核心逻辑。

场景1:协程正常执行

当协程未发生错误时。resume的返回值格式为:

true , val1 , val2 ,....

  • 第一个返回值固定为true,表示执行正常;
  • 后续的val1, val2, ...分两种情况:
    1. 若协程因yield挂起:返回coroutine.yield参数(即协程希望传递给外部的值);
    2. 若协程执行完毕:返回协程主体函数的返回值

示例 1:协程因 yield 挂起(返回 yield 的参数)

co = coroutine.create(function ()-- yield传递两个值:10、20coroutine.yield(10, 20)
end)
-- resume返回true,以及yield的参数10、20
print(coroutine.resume(co))  --> true  10  20

示例 2:协程执行完毕(返回主体函数的返回值)

co = coroutine.create(function ()-- 主体函数返回30、40return 30, 40
end)
-- resume返回true,以及主体函数的返回值30、40
print(coroutine.resume(co))  --> true  30  40

场景2:协程执行出错

当协程执行发生错误,resume的返回值格式为:

false ,错误信息

  • 第一个返回值固定为false,表示执行出错;
  • 第二个返回值是字符串类型的错误信息,描述错误原因。
co = coroutine.create(function ()-- 故意调用未定义的函数,触发错误undefinedFunc()
end)
-- resume返回false,以及错误信息
print(coroutine.resume(co))  
--> false  [string "function () undefinedFunc() end"]:2: attempt to call global 'undefinedFunc' (a nil value)

三、管道与过滤器

核心是利用协程的resume-yeild机制解决传统生产者消费者“谁是主导主循环”问题,并进一步扩展出“过滤器”组件,实现数据生成,处理,消费全流程协同,同是对比了与UNIX管道的异同。

3.1核心问题:传统生产者-消费者“主循环冲突”

传统的生产者消费者模型中,生产者和消费者各自拥有独立主循环,均视为对方为“可调用服务”,存在“谁主导执行流程”的矛盾。

--生产者:不断产生值(读文件),通过send发送给消费者function producer()while true do local x = io.read()   --生产值send(x)               --发送给消费者(核心矛盾:send如何匹配receive)end
end--消费者:不断通过receive接收值,然后消费
function consumer()while true dolocal x = receive()  --从消费者接收值(核心矛盾:receive如何触发生产者?)io.write(x,"\n")     --消费end
end

核心问题:生产者和消费者均为“主动执行”,无法直接匹配send与receive的调用关系——若让生产者主导,消费者需被动等待;若让消费者主导,生产者需被动响应,传统函数调用难以协调。

3.2解决方案用resmue匹配send-receive

协程的resume-yeild机制可反转“调用-被调用”关系,让send和receive都认为自己是“主动方”,从而解决主循环冲突。具体通过receive和send函数,将生产者封装为协程,由消费者主导执行流程。

(1)receive和send

receive(prod):消费者通过此函数 “唤醒生产者” 并获取值。内部调用coroutine.resume(prod)启动 / 恢复生产者协程,接收生产者通过yield传递的值。

function receive(prod)local status, value = coroutine.resume(prod)  --唤醒生产者协程return value  --返回生产者产生的新值
end

send(x):生产者通过此函数 “传递值并挂起”。内部调用coroutine.yield(x),将值x传递给receive,同时挂起自身,等待下一次被唤醒。

function send(x)coroutine.yield(x) --传递值给receive,挂起生产者
end

(2)生产者封装成协程

生产者不再是独立主循环,而是被封装为协程(通过coroutine.create创建),需通过receive唤醒才会执行并产生值。书中代码示例:

function producer()return coroutine.create(function()while true dolocal x=io.read()send(x)endend)
end

(3)消费者主导执行

消费者通过receive主动唤醒生产者获取值,主导整个流程:当消费者需要新值时,调用receive唤醒生产者,生产者产生值后挂起,消费者继续消费。完整调用逻辑:

function coneumer(prod)while true dolocal x = receive(prod)io.write(x, "\n")end
end--启动流程
local p = producer()
consumer(p)

四、过滤器

过滤器组件:介于生产者和消费者之间,即作为“消费者”从生产者获取值,又作为“生产者”处理数据后传递给消费者,实现数据的中间转换。

过滤器的逻辑:以 “给每行数据加行号” 为例,过滤器需:

  1. 从生产者获取原始数据(作为消费者);
  2. 对数据进行处理(加行号);
  3. 将处理后的数据传递给消费者(作为生产者)。
function filter(prod)--返回过滤器的协程:从生产者拿值,加行号传递给消费者return coroutine.create(function()local line = 1 --行号计数器while true do local x = receive(prod) --从生产者获取原始值(消费者角色)x = string.format("%5d %s",line,x) --处理:加行号send(x)        --将处理后的值传递给消费者(生产者角色)line = line + 1 --行号自增endend)
end

串联生产者 - 过滤器 - 消费者

三者通过协程串联,仍由消费者主导执行,流程为:消费者→过滤器→生产者。书中简化调用代码:

lua

-- 串联:生产者→过滤器→消费者,消费者主导
consumer(filter(producer()))

执行逻辑:消费者需要值时,唤醒过滤器;过滤器需要值时,唤醒生产者;生产者产生值→过滤器处理→消费者消费,形成完整数据流转。

举个例子理解:

-- 基于《Lua程序设计第二版》9.2节“管道与过滤器”核心逻辑:单生产者-单过滤器-单消费者完整实现
-- 1. 基础通信函数(对应书中receive/send,实现协程间数据交互)
function receive(prod)local status, value = coroutine.resume(prod)return value
end
function send(x)coroutine.yield(x)
end-- 2. 生产者(协程):生成原始数据(模拟从输入读取文本行,书中9.2节用io.read,此处简化为固定3行原始数据)
function producer()return coroutine.create(function()local raw_data = {"Lua协程管道示例", "原始数据行1", "原始数据行2"}for _, line in ipairs(raw_data) dosend(line)  -- 传递原始数据并挂起,等待过滤器唤醒endsend(nil)  -- 传递nil标识数据结束end)
end-- 3. 过滤器(协程):中间层数据处理(对应书中“加行号”逻辑,给原始数据添加行号前缀)
function filter(prod)return coroutine.create(function()local line_num = 1  -- 行号计数器while true dolocal raw_line = receive(prod)  -- 从生产者获取原始数据(过滤器扮演“消费者”)if raw_line == nil thensend(nil)  -- 向下游传递结束信号breakendlocal processed_line = string.format("[第%d行] %s", line_num, raw_line)  -- 数据处理:加行号send(processed_line)  -- 传递处理后的数据给消费者(过滤器扮演“生产者”)line_num = line_num + 1endend)
end-- 4. 消费者(主导流程):从过滤器获取处理后的数据并消费(模拟打印输出,书中9.2节用io.write)
function consumer(prod)print("===== 消费者开始消费 =====")while true dolocal processed_line = receive(prod)  -- 唤醒过滤器获取处理后的数据if processed_line == nil thenprint("===== 消费者结束消费 =====")breakendprint("消费数据:", processed_line)  -- 消费动作:打印end
end-- 5. 启动流程(串联生产者→过滤器→消费者,符合书中“消费者驱动”逻辑)
consumer(filter(producer()))

代码流程图

http://www.dtcms.com/a/546129.html

相关文章:

  • 建立网站目录结构的原则优化二十条
  • 远近互联网站建设成品网站源码1688danji6
  • 枣庄三合一网站开发公司软件开发模型的v模型图
  • 生成式人工智能在教育领域的技术适配性研究:挑战、风险与应对方案
  • 技术解析:AI出海两极分化下的破局指南
  • CAN总线网关到底是什么:双5g车载网关案例
  • GifCam,一款小巧的GIF录制工具
  • 如何让脚本同时兼容Python2和Python3?
  • 永嘉网站建设工作室口腔建立网站
  • 莱芜十大首富多说评论插件对网站优化
  • 网站开发的主要内容亚马逊跨境电商开店
  • Linux 进程退出和进程控制
  • 计算机网络自顶向下方法14——应用层 DNS详解 工作机理
  • [GXDE软件包安装器]在龙芯deepin上一键安装旧世界包
  • dw设计模板系统优化的方法知识点
  • 【AI】人工智能之PINN和贝叶斯
  • 毕业设计答辩网站开发原理江苏省网站建设
  • 网站备案照湘潭做网站选择磐石网络
  • 如何做网站的二级页面500网站建设
  • Python 高效实现 Excel 与 TXT 文本文件之间的数据转换
  • mysql重置密码
  • Spring MVC 数据校验
  • Rust 中所有权与零成本抽象的深度关联:原理与实践
  • 被通知公司网站域名到期搭建企业资料网站
  • 仓颉语言宏系统的设计与应用:从元编程到领域特定语言
  • 【GUI】本地电脑弹出远程服务器的软件GUI界面
  • 仓颉技术:Union类型的定义与应用
  • 闲置电脑做网站服务器重庆互联网公司排行榜
  • 连续值和缺失值详解
  • 仓颉FFI外部函数接口:跨语言互操作的工程实践