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

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

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

相关推荐
MSTcheng.25 分钟前
【C语言】指针(5)
c语言·开发语言
╮壞孩子的天26 分钟前
C语言多人聊天室 ---chat(客户端聊天)
c语言·开发语言
楼台的春风2 小时前
【STM32 基于PID的闭环电机控制系统】
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·算法
free-elcmacom2 小时前
C语言番外篇(3)------------>break、continue
c语言·开发语言
EnigmaCoder3 小时前
单链表:数据结构中的灵活“链条”
c语言·数据结构·学习
柠石榴3 小时前
【练习】【类似于子集问题】力扣491. 非递减子序列/递增子序列
c++·算法·leetcode·回溯
Ronin-Lotus4 小时前
程序代码篇---C/C++中的变量存储位置
c语言·c++···静态区·文字常量区·变量存储位置
Abdullah al-Sa4 小时前
Docker教程(喂饭级!)
c++·人工智能·docker·容器
进击的_鹏4 小时前
【C++】list 链表的使用+模拟实现
开发语言·c++·链表
C语言扫地僧5 小时前
RPC 框架项目剖析
c++·网络协议·学习·rpc