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 102.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, ...分两种情况:
- 若协程因
yield挂起:返回coroutine.yield的参数(即协程希望传递给外部的值);- 若协程执行完毕:返回协程主体函数的返回值。
示例 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)四、过滤器
过滤器组件:介于生产者和消费者之间,即作为“消费者”从生产者获取值,又作为“生产者”处理数据后传递给消费者,实现数据的中间转换。
过滤器的逻辑:以 “给每行数据加行号” 为例,过滤器需:
- 从生产者获取原始数据(作为消费者);
- 对数据进行处理(加行号);
- 将处理后的数据传递给消费者(作为生产者)。
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()))代码流程图

