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

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

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

相关推荐
波音彬要多做5 分钟前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
捕鲸叉6 分钟前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
Schwertlilien1 小时前
图像处理-Ch5-图像复原与重建
c语言·开发语言·机器学习
只做开心事1 小时前
C++之红黑树模拟实现
开发语言·c++
程序员buddha2 小时前
C语言从入门到放弃教程
c语言·开发语言
程序员老冯头2 小时前
第十五章 C++ 数组
开发语言·c++·算法
程序猿会指北3 小时前
【鸿蒙(HarmonyOS)性能优化指南】启动分析工具Launch Profiler
c++·性能优化·harmonyos·openharmony·arkui·启动优化·鸿蒙开发
AAA.建材批发刘哥6 小时前
Linux快速入门-Linux文件系统管理
linux·运维·服务器·c语言·学习方法
无 证明7 小时前
new 分配空间;引用
数据结构·c++
Kisorge8 小时前
【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数
c语言·开发语言