一、协程
Lua 中使用半协程的方式进行组织代码。
和线程的最大区别在于,一个多线程程序可以并行运行多个线程,而协程却需要彼此协作运行,即任意指定时刻只能一个协程运行,且只有当正在运行的协程显式地要求被挂起时,该协程才会被暂停。
二、协程的创建和使用
Lua 的协程创建非常简单,只需要通过 coroutine.create(f)
函数即可创建,但是创建完的协程并不会自动运行,还需要使用 coroutine.resume(co, val1, ...)
函数进行运行。
1、coroutine.create(f)
创建一个新的协程,主体为 f 函数。
参数:
- f:是一个 Lua 函数,即协程运行的代码。
返回值:
返回新创建的协程,是一个类型为 thread 的对象
举个例子
local co = coroutine.create(function()
print("Hello, jiang pengyong.")
end)
print("type(coroutine)", type(co)) --> type(coroutine) thread
2、coroutine.resume(co, val1, …)
开始或继续协程 co
的执行。
当对协程 co
第一次使用 resume
启动协程时,会开始运行它的主体,会将 (val1
, …) 作为参数传递给主体函数。
如果协程 co
已经挂起(通过 yield 进行挂起,下面会分享),则 resume
会重新启动它(从挂起点开始运行),并将 (val1
, …) 作为 yield 的参数传递。
参数:
- co:要被启动或重新启动的协程
- val1,…:“作为启动的参数传递给协程” 或 “作为 yield 的接受参数”。
返回值:
- 如果协程运行没有任何错误,则会返回 true 加上 “
yield
函数抛出的参数值” 或是 “当协程终止时协程主体函数返回的值”。 - 如果有任何错误,则返回 false 加上错误信息。
举个例子
local co = coroutine.create(function()
print("Hello, jiang pengyong.")
end)
coroutine.resume(co)
--> Hello, jiang pengyong.
三、协程的状态
协程总共有四种状态:挂起(suspended)、运行(running)、正常(normal)、死亡(dead)。
可以通过 coroutine.status(co)
进行查看协程的状态。
1、coroutine.status(co)
获取协程 co
的状态,返回值以字符串表示:
running
:协程正在运行suspended
:协程在调用yield
时被挂起,或者还没有开始运行normal
:协程处于活动状态但未运行,即它已启动另一个协程(在该协程内调用了 resume 启动另一个协程)dead
:协程已完成其主体功能,或者因错误停止
下面一一阐述各种状态:
2、挂起(suspended)
处于这一状态的两种情况:
- 第一种情况:协程刚被创建时(协程不会自动运行),该协程就处于这个状态。
- 第二种情况:协程内部调用了
yield
方法,将自身挂起,此时也处于这个状态。
刚创建的协程
local co = coroutine.create(function()
print("Hello, jiang pengyong.")
end)
print("status(coroutine)", coroutine.status(co)) --> status(coroutine) suspended
使用 yield 进行挂起
local co = coroutine.create(function()
coroutine.yield()
end)
coroutine.resume(co)
print(coroutine.status(co)) --> suspended
3、运行(running)
当一个协程被运行起来后,他的状态就为运行状态了
co = coroutine.create(function()
print(coroutine.status(co)) --> running
print("Hello, jiang pengyong.") --> Hello, jiang pengyong.
end)
coroutine.resume(co)
4、正常(normal)
当一个协程 A 内部启动了另一个协程 B ,此时 A 处于正常状态, B 处于运行状态
local B
local A = coroutine.create(function()
print("run A") --> run A
coroutine.resume(B)
print("end A") --> end A
end)
B = coroutine.create(function()
print("start B") --> start B
print("A status", coroutine.status(A)) --> A status normal
print("B status", coroutine.status(B)) --> B status running
print("end B") --> end B
end)
coroutine.resume(A)
5、死亡(dead)
当协程执行完了函数,就是这状态
co = coroutine.create(function()
print(coroutine.status(co)) --> running
print("Hello, jiang pengyong.") --> Hello, jiang pengyong.
end)
coroutine.resume(co)
print("status(coroutine)", coroutine.status(co)) --> status(coroutine) dead
四、coroutine.running()
可以通过 coroutine.running()
进行获取当前的协程
返回值:
有返回两个值:
- 第一个是正在运行的协程。
- 第二个是一个布尔值,表示正在运行的协程是否为主协程,为主协程时为真。
print("coroutine.running() 主协程", coroutine.running()) --> coroutine.running() 主协程 thread: 0x7f8f1d808e08 true
local co = coroutine.create(function()
print("coroutine.running()", coroutine.running()) --> coroutine.running() thread: 0x6000020e4278 false
end)
print(coroutine.resume(co))
五、协程的传值
协程的传值由两个函数进行 resume
和 yield
,resume
已经在前面介绍了,这里介绍下 yield
1、coroutine.yield(…)
挂起当前协程,yield
会将参数(…)都作为结果传递给 resume
。
2、使用
local co = coroutine.create(function(x)
print("接收第一次参数:", x)
a, b, c, d, e = coroutine.yield("第一次返回值")
print("接收第二次参数:", a, b, c, d, e)
return "第二次返回值"
end)
print(coroutine.resume(co, "hi"))
print(coroutine.resume(co, 4, 5, "江澎涌"))
--> 接收第一次参数: hi
--> true 第一次返回值
--> 接收第二次参数: 4 5 江澎涌 nil nil
--> true 第二次返回值
当协程还没有启动的时候,则会将 resume
第二个开始携带的参数当作协程的方法参数。
当协程内部调用 yield
时,协程会挂起。在挂起协程的同时,将 yield
的参数作为返回值给到外部启动该协程的 resume
。
当外部再次启动协程时,同样会将 resume
第二个开始携带的参数当作入参给到 yield 函数,可以理解为 yield 的返回值。
最后当协程方法运行完后,如果有 return 将值返回,则会被用作 resume 的返回值。
3、一图胜千言
可以通过下图理解 create
、resume
、yield
即协程返回值间的关系
六、对协程异常的捕获
resume 方法是运行在保护模式中的,所以一旦协程内部发生错误,则会抛给 resume
方法,resume
方法会得到两个返回值,一个是 false,一个是错误原因。
local co = coroutine.create(function()
error("协程内部错误")
coroutine.yield()
end)
print(coroutine.resume(co)) --> false ...ong/Desktop/study/lua_study_2022/17 协程/coroutine.lua:50: 协程内部错误
print(coroutine.status(co)) --> dead
七、coroutine.wrap(f)
wrap 和 create 有些类似,只是 wrap 返回的不是协程,而是一个函数,每次调用这个函数,则会相当于这个协程被 resume 了一次。还有当协程方法内部发生异常,则会导致抛出异常,不会被保护模式所获取。
对于传值方面,和 yield-resume 是一样的。
local co = coroutine.wrap(function(name)
print(string.format("Hello, %s.", name)) --> Hello, jiang pengyong.
local age = coroutine.yield("Hi.") --> Hi.
print(age) --> 29
return "Bye."
end)
print(co("jiang pengyong")) --> Hi.
print(co("29")) --> Bye.
1、coroutine.wrap(f) 和 coroutine.create(f)
coroutine.wrap(f) 使用简单,只是一个唤醒函数。
coroutine.create(f) 可以更加灵活的控制,例如可以获取协程的状态,捕获异常。
八、coroutine.isyieldable()
检测正在运行的协程是否可以挂起,如果可以挂起则为 true。
如果运行中的协程不是主线程并且不在不能挂起的 C 函数中,则他是可挂起的。
local co = coroutine.create(function()
print(coroutine.isyieldable()) --> true
print("Hello, jiang pengyong.") --> Hello, jiang pengyong.
end)
coroutine.resume(co)
print(coroutine.isyieldable()) --> false
九、写在最后
Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)
如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀。
公众号搜索 “江澎涌”,更多优质文章会第一时间分享与你。