Lua 协程

一、协程

Lua 中使用半协程的方式进行组织代码。

和线程的最大区别在于,一个多线程程序可以并行运行多个线程,而协程却需要彼此协作运行,即任意指定时刻只能一个协程运行,且只有当正在运行的协程显式地要求被挂起时,该协程才会被暂停。

二、协程的创建和使用

Lua 的协程创建非常简单,只需要通过 coroutine.create(f) 函数即可创建,但是创建完的协程并不会自动运行,还需要使用 coroutine.resume(co, val1, ...) 函数进行运行。

1、coroutine.create(f)

创建一个新的协程,主体为 f 函数。

参数:

  • f:是一个 Lua 函数,即协程运行的代码。

返回值:

返回新创建的协程,是一个类型为 thread 的对象

举个例子

lua 复制代码
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 加上错误信息。

举个例子

lua 复制代码
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 方法,将自身挂起,此时也处于这个状态。

刚创建的协程

lua 复制代码
local co = coroutine.create(function()
    print("Hello, jiang pengyong.")
end)
print("status(coroutine)", coroutine.status(co))    --> status(coroutine)	suspended

使用 yield 进行挂起

lua 复制代码
local co = coroutine.create(function()
    coroutine.yield()
end)
coroutine.resume(co)
print(coroutine.status(co))     --> suspended

3、运行(running)

当一个协程被运行起来后,他的状态就为运行状态了

lua 复制代码
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 处于运行状态

lua 复制代码
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)

当协程执行完了函数,就是这状态

lua 复制代码
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() 进行获取当前的协程

返回值:

有返回两个值:

  • 第一个是正在运行的协程。
  • 第二个是一个布尔值,表示正在运行的协程是否为主协程,为主协程时为真。
lua 复制代码
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))

五、协程的传值

协程的传值由两个函数进行 resumeyieldresume 已经在前面介绍了,这里介绍下 yield

1、coroutine.yield(...)

挂起当前协程,yield 会将参数(...)都作为结果传递给 resume

2、使用

lua 复制代码
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、一图胜千言

可以通过下图理解 createresumeyield 即协程返回值间的关系

六、对协程异常的捕获

resume 方法是运行在保护模式中的 ,所以一旦协程内部发生错误,则会抛给 resume 方法,resume 方法会得到两个返回值,一个是 false,一个是错误原因。

lua 复制代码
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 是一样的。

lua 复制代码
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 函数中,则他是可挂起的。

lua 复制代码
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吧,码字不易,请多多支持)

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀

公众号搜索 "江澎涌",更多优质文章会第一时间分享与你。

相关推荐
枫叶丹41 小时前
【Qt开发】Qt系统(一)-> 定时器 QTimerEvent 和 QTimer
c语言·开发语言·数据库·c++·qt·系统架构
xu_yule3 小时前
算法基础(数论)—费马小定理
c++·算法·裴蜀定理·欧拉定理·费马小定理·同余方程·扩展欧几里得定理
宇宙超级无敌暴龙战士7 小时前
旮旯c语言三个任务
c++·c
BanyeBirth8 小时前
C++差分数组(二维)
开发语言·c++·算法
Fcy6488 小时前
C++ map和multimap的使用
开发语言·c++·stl
CC.GG8 小时前
【C++】STL容器----unordered_map和unordered_set的使用
java·数据库·c++
lengjingzju9 小时前
基于IMake的 GCC 编译与链接选项深度解析:构建高效、安全、可调试的现代软件
c++·安全·性能优化·软件构建·开源软件
xu_yule10 小时前
算法基础(数论)—算法基本定理
c++·算法·算数基本定理
CoderCodingNo10 小时前
【GESP】C++五级真题(结构体排序考点) luogu-B3968 [GESP202403 五级] 成绩排序
开发语言·c++·算法
自然常数e12 小时前
字符函数和字符串函数
c语言·算法·visual studio