1、 Lua 的协同程序(coroutine)简单介绍
Lua 的协同程序(coroutine)是一种轻量级的线程,允许你在多个任务之间进行协作式多任务处理。与操作系统线程不同,协同程序是由程序员显式控制的,不会自动切换,而是通过 yield
和 resume
来手动切换执行权。
协同程序的核心思想是协作式多任务,即一个任务主动让出执行权,另一个任务才能继续执行。这种机制非常适合需要分步执行的任务,例如状态机、迭代器、异步任务等。
-
创建协同程序:
-
使用
coroutine.create(f)
创建一个协同程序,其中f
是一个函数。 -
返回的是一个协同程序对象(类型为
thread
)。如果看过我之前的文章,其实这个有点像pthread_create,即创建一个线程,但是两个是不一样的东西
-
-
启动或恢复协同程序:
-
使用
coroutine.resume(co, ...)
启动或恢复一个协同程序。 -
可以传递参数给协同程序。
-
-
让出执行权:
- 在协同程序内部,使用
coroutine.yield(...)
让出执行权,并返回一些值给调用者。
- 在协同程序内部,使用
-
检查协同程序状态:
-
使用
coroutine.status(co)
检查协同程序的状态:-
"running"
:正在运行。 -
"suspended"
:暂停(等待恢复)。 -
"dead"
:已经结束。
-
-
-
获取当前运行的协同程序:
- 使用
coroutine.running()
获取当前正在运行的协同程序。
- 使用
2、lua的协同程序和pthread的区别
pthread_create
和 Lua 的协同程序(coroutine)是两种完全不同的并发机制,它们的实现方式、使用场景和行为都有显著区别。
1. pthread_create
pthread_create
是 POSIX 线程(pthread)库中的一个函数,用于创建操作系统级别的线程。
-
操作系统线程 :
pthread_create
创建的是真正的操作系统线程,由操作系统调度。线程是抢占式的,操作系统会在任意时刻切换线程的执行。 -
并发性:多个线程可以并行运行(如果有多核 CPU)。线程之间是真正并发的。
-
资源开销:线程的创建和切换需要较大的资源开销(内存、上下文切换等)。线程数量受操作系统限制。
-
同步和通信 :线程之间需要通过锁(如
pthread_mutex
)、条件变量(pthread_cond
)等机制进行同步和通信。容易出现竞态条件(race condition)和死锁(deadlock)。 -
使用场景:适合需要真正并行执行的场景,例如 CPU 密集型任务或需要利用多核性能的任务。
2. Lua 协同程序(coroutine)
Lua 的协同程序是一种用户态的轻量级线程:
-
用户态线程:
-
协同程序是由 Lua 虚拟机管理的,不依赖操作系统线程。
-
协同程序是协作式的,需要显式调用
yield
和resume
来切换任务。
-
-
并发性:
-
协同程序是单线程的,同一时刻只有一个协同程序在运行。
-
无法利用多核 CPU 实现真正的并行。
-
-
资源开销:
-
协同程序的创建和切换开销非常小,适合高并发场景。
-
可以创建成千上万个协同程序。
-
-
同步和通信:
-
协同程序之间不需要锁或条件变量,因为它们是协作式的。
-
数据共享更简单,不容易出现竞态条件。
-
-
使用场景:
-
适合 I/O 密集型任务、状态机、迭代器等需要分步执行的场景。
-
不适合 CPU 密集型任务。
-
3、协同程序的相关函数解析
1. coroutine.create(f)
-
功能: 创建一个新的协同程序。
-
参数 :
f
: 一个函数,作为协同程序的主体。 -
返回值 :返回一个协同程序对象(类型为
thread
)。
2. coroutine.resume(co, ...)
-
功能: 启动或恢复一个协同程序的执行。
-
参数 :
co
: 协同程序对象(由coroutine.create
创建)。...
: 可选参数,传递给协同程序的参数。 -
返回值 :第一个返回值是一个布尔值,表示协同程序是否成功执行。后续返回值是协同程序通过
coroutine.yield
或return
返回的值。
3.coroutine.yield(...)
-
功能: 暂停当前协同程序的执行,并返回一些值给调用者。
-
参数 :
...
: 可选参数,作为yield
的返回值。 -
返回值 :无返回值(因为
yield
会暂停执行)。 -
注意:只能在协同程序内部调用。
4.coroutine.status(co)
-
功能: 获取协同程序的当前状态。
-
参数 :
co
: 协同程序对象。 -
返回值:返回一个字符串,表示协同程序的状态:
-
"dead"
: 已经结束。 -
"suspended"
: 暂停(等待恢复)。 -
"running"
: 正在运行。
-
5.coroutine.running()
-
功能: 获取当前正在运行的协同程序。
-
参数:无。
-
返回值 :返回当前正在运行的协同程序对象。如果当前不在协同程序中,返回
nil
。
6.coroutine.wrap(f)
-
功能: 创建一个新的协同程序,并返回一个函数,调用该函数会恢复协同程序的执行。
-
参数 :
f
: 一个函数,作为协同程序的主体。 -
返回值 :返回一个函数,调用该函数相当于调用
coroutine.resume
。 -
注意:
- 与
coroutine.create
不同,coroutine.wrap
返回的函数会直接返回yield
的值,而不是布尔状态。
- 与
看下面这个代码,会输出什么,如果按照c语言当中的语法规则,这里可能会直接输出开始执行,然后停止,但是这是在lua语法规则当中,所以对于这里来说,会输出nil,nil,也就是两个空,那么为什么会这样呢,Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。这里我们只是定义了一个函数,函数 foo
中的输出没有执行,是因为 Lua 的协同程序是惰性执行 的。也就是说,协同程序不会自动执行 ,必须通过 coroutine.resume
显式地启动或恢复它。:
Lua
function foo()
print("开始执行")
local value=coroutine.yield("暂停执行")
print("协同恢复执行")
print("结束执行")
end
--local co=coroutine.create(foo)
--local status,result=coroutine.resume(co)
print(result)
--status,result=coroutine.resume(co,42)
print(result)
如果将注释去掉,这样之后就能够执行代码了,这时候协同程序被创建
Lua
function foo()
print("开始执行")
local value=coroutine.yield("暂停执行")
print("协同恢复执行,传入"..tostring(value))
print("结束执行")
end
local co=coroutine.create(foo)
local status,result=coroutine.resume(co)
print(status,result)
status,result=coroutine.resume(co,42)
print(status,result)
local status, result = coroutine.resume(co)
print(status,result) -- 输出: true,暂停执行
-- 恢复协同程序,并传入一个值
status, result = coroutine.resume(co, 42)
这三行代码如何理解呢?看resume这个函数,这个函数被调用了两次,第一次用来启动协同程序,而且resume这个代码来说有两个返回值,第一个返回值给到status,如果执行成功,返回true,第二个返回值给到result,返回的是暂停执行。那么问题来了,为什么返回值是yield函数的参数呢,这是因为,协同程序是一个特殊的线程,也就是单线程,一次只能运行一个线程,不是并行执行,如果是操作系统里面的线程执行,会跳转到线程当中执行,并且当前代码也会继续执行,但是在lua当中,线程执行只能有一个,而这里会在foo函数当中继续执行,直到遇到暂停函数,这时候会暂停线程,回来执行下面的代码,也就是print函数,这时候打印出来的值就是yield函数的参数,第二次使用resume是用来恢复协同程序,因为上面的协同程序被暂停了,这时候会再次恢复协同程序,但是不会在刚才已经执行过的代码继续执行,而是执行已经执行过的下一行代码。那么如果这时候再有一个resume函数呢?看下面的代码:
Lua
function foo()
print("开始执行")
local value=coroutine.yield("暂停执行")
print("协同恢复执行,传入"..tostring(value))
print("结束执行")
end
local co=coroutine.create(foo)
local status,result=coroutine.resume(co)
print(status,result)
status,result=coroutine.resume(co,42)
print(status,result)
status,result=coroutine.resume(co,42)
print(result)
这段代码当中拥有三个resume函数,我们知道,第一和第二个是用来启动和恢复协同线程的,这时候第三个会受到影响吗?
直接看输出结果:
Lua
开始执行
true 暂停执行
协同恢复执行,传入42
结束执行
true nil
cannot resume dead coroutine
这时候的输出结果出现了一句cannot resume dead corutine 也就是说,cannot resume dead coroutine
是 Lua 中的一个错误提示,表示你尝试恢复一个已经**死亡(dead)**的协同程序。协同程序一旦执行完毕(即函数返回或执行到末尾),就会进入 dead
状态,此时再调用 coroutine.resume
就会触发这个错误。这是一个错误提示。