【Lua学习笔记】Lua进阶——协程

在这里插入图片描述

文章目录

  • 协程
    • 协程的定义和调度
      • wrap
    • Status
    • Running
    • 补充
  • 协程与主程的数据交互——return...yield


协程

协程是一种并发操作,相比于线程,线程在执行时往往是并行的,并且线程在创建销毁执行时极其消耗资源,并且过长的执行时间会造成主进程阻塞。而协程可以以并发时轮值时间片来执行,优点是不会阻塞,消耗资源少,可以手动控制。至于协程和线程的区别,什么是并发并行,请自行查阅或者学习操作系统理论知识。

协程的定义和调度

在Unity中,由于游戏是单线程的(这是为了其他线程不阻塞进程),因此我们常常需要使用协程。在Lua中也支持协程,例如下列代码:

Corou1 =coroutine.create(function ()
    print("我是协程1")
end)
print(Corou1)
Corou2 = coroutine.wrap(function ()
    print("我是协程2")
end)
print(Corou2)
coroutine.resume(Corou1) --开始或继续协程
Corou2()   	--运行函数
输出:
thread: 00CAEC00
function: 00CA0578
我是协程1
我是协程2

我们可以用两种不同的方法创建协程,不过返回值一个是协程(虽然thread是线程),一个是函数。所以对这两个协程的运行方式也不同。如何理解这两种方式?我想用C#的例子:

void StartCorou()  --function类型
{
    StartCoroutine(Coroutine());  --thread类型直接执行
}

IEnumerator Coroutine()
{
}

thread类型定义的就是协程本身,因此我们可以用lua提供的协程table中的方法来调用,而function类型更像是定义了一个启动函数,通过调用这个函数来执行。

在Lua中的协程有一个优点:我们可以手动执行

Corou1 =coroutine.create(function ()
    local i = 1
    while true do
        print(i)
        i = i + 1
        coroutine.yield(i)  --yield也可以返回
    end
end)

coroutine.resume(Corou1)
a,b = coroutine.resume(Corou1)
print(a,b)
coroutine.resume(Corou1)

输出:
1
2
true	3
3 

通过注释我们可以发现,resume第一个返回固定为成功判断,然后返回yield提供的变长参数
function coroutine.resume(co: thread, val1?: any, ...any)
  -> success: boolean
  2. ...any

可以看到,对于进入了yield的协程,需要我们手动执行resume才能继续执行。而非自动地轮换时间片。这样有利有弊,但我认为在调度上更加灵活了。

wrap

使用wrap的话:

function f1()
    local i = 1
    while true do
        print(i)
        i = i + 1
        coroutine.yield(i) 
    end
end
b = coroutine.wrap(f1)
print("返回值"..b())
print("返回值"..b())
print("返回值"..b())

输出:
1
返回值2
2
返回值3
3
返回值4

function coroutine.wrap(f: fun(...any):...unknown)
  -> fun(...any):...unknown

使用wrap则我们发现,wrap并不返回成功判断,而是直接返回yield给出的变长参数,这是由于其内定义的函数就只接收变长参数。

根据菜鸟教程的描述,使用wrap将创建一个协程包装器,并将协同程序函数转换为一个可直接调用的函数对象

-- 使用 coroutine.wrap 创建了一个协同程序包装器,将协同程序函数转换为一个可直接调用的函数对象
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1) --输出1

Status

Lua中的协程总共有四种状态:

function coroutine.status(co: thread)
  -> "dead"|"normal"|"running"|"suspended"
以字符串形式返回协程 co 的状态。

return #1:
    | "running"  正在运行。
    | "suspended"  挂起或是还没有开始运行。
    | "normal"  是活动的,但并不在运行。
    | "dead"  运行完主体函数或因错误停止。

让我们从下面的例子看看协程什么时候会进入这些状态:

C2 =  coroutine.create(function ()
    print("协程2执行")
    print("此时协程1:"..coroutine.status(Corou1))
    coroutine.yield()
end)
Corou1 =coroutine.create(function ()
    local i = 1
    while i<3 do
        print(i)
        i = i + 1
        if i==2 then
            coroutine.resume(C2)
        end
        print(coroutine.status(Corou1))
        coroutine.yield() 
    end
end)
print(coroutine.status(Corou1))
print("######")
coroutine.resume(Corou1)
print(coroutine.status(Corou1))
print("######")
coroutine.resume(Corou1)
print(coroutine.status(Corou1))
print("######")
coroutine.resume(Corou1)
print(coroutine.status(Corou1))
print("######")

输出:
suspended
######
1
协程2执行
此时协程1:normal
running
suspended
######
2
running
suspended
######
dead
######

从上述例子中看到,当协程未启动以及暂停中等待下一次resume时,其状态是suspended。而当协程正在执行时状态是running,当协程1执行切换到协程2时,协程1的状态是normal。最后当协程1退出时其状态是dead。


Running

running函数可以得到当前正在运行的协程(的地址)。

function coroutine.running()
  -> running: thread
  2. ismain: boolean
返回当前正在运行的协程加一个布尔量。 如果当前运行的协程是主线程,其为真。
(我试了一下没接收到这个bool,不知道什么情况)

用例:

Corou1 =coroutine.create(function ()
    local i = 1
    while i<3 do
        print(i)
        i = i + 1
        print("协程1状态:"..coroutine.status(Corou1))
        print(coroutine.running())
        coroutine.yield() 
    end
end)
coroutine.resume(Corou1)

输出:
1
协程1状态:running
thread: 00A7E1F8  <-- 协程1的地址

先抛出一个问题:如果在上述的同样代码中,执行协程2时print这个running函数,请问可以得到什么?考虑到协程1在协程2运行时是normal状态,当前运行(running)的协程应当是协程2

C2 =  coroutine.create(function ()
    print("协程2执行")
    print(coroutine.running())
    print("此时协程1:" .. coroutine.status(Corou1))
    print("协程2执行结束")
    coroutine.yield()
end)
Corou1 =coroutine.create(function ()
    local i = 1
    while i<3 do
        print(i)
        i = i + 1
        if i==2 then
            coroutine.resume(C2)
        end
        print("协程1状态:"..coroutine.status(Corou1))
        print(coroutine.running())
        coroutine.yield() 
    end
end)
coroutine.resume(Corou1)
coroutine.resume(Corou1)

输出:
1
协程2执行
thread: 00C4CFE8
此时协程1:normal
协程2执行结束
协程1状态:running
thread: 00C4D408
2
协程1状态:running
thread: 00C4D408

补充

在协程第一次被执行之后,当第二次执行该协程时不需要输入所需的参数,例如下面的例子

Corou1 =coroutine.create(function (a,b)
    coroutine.yield(a + b, a - b)
    return a*b,a/b
end)
r1, r2, r3 = coroutine.resume(Corou1,20,10)
print(r1, r2, r3)
r1, r2, r3 = coroutine.resume(Corou1)
print(r1, r2, r3)
r1, r2, r3 = coroutine.resume(Corou1,20,10)
print(r1, r2, r3)
输出:
true	30	10 --执行yield
true	200	2.0 --执行return
false	cannot resume dead coroutine	nil

此外从上例也可以看出,执行协程也可以以return作为函数最后的返回值。而且一个已经dead的协程是不会被resume执行的。如果还需要调用这个已经死掉的协程,请重新创建一个,否则报错。


协程与主程的数据交互——return…yield

综合以上知识,我们看看菜鸟教程里的这个例子:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)
    return "协程结束"
end)
       
print(coroutine.resume(co, 1, 10)) -- true, 4
print(coroutine.resume(co)) -- true, 协程结束

输出:
第一次协同程序执行输出	1	10
foo 函数输出	2
true	4
true	协程结束

在上例中,协程co的语句中并无yield返回,而是将其放在foo函数之中,让函数执行完毕时return yield。所以这个协程在r=foo(a+1)执行完毕后就suspend了。那么究竟执行顺序是如何呢?

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)  --协程停止
    
    print("第二次协同程序执行输出", r)
    local r, s = coroutine.yield(a + b, a - b)  -- a,b的值为第一次调用协同程序时传入   --resume
    
    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入
end)
       
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4
--分割线----
第二次协同程序执行输出	r
main	true	11	-9
---分割线---
第三次协同程序执行输出	x	y
main	true	10	结束协同程序
---分割线---
main	false	cannot resume dead coroutine
---分割线---

上面的例子更复杂了,而且出现了一个我没意料到的情况:

第二次协同程序执行输出	r

在第一次协程执行时,r应当等于4,而现在我们将输入的字符“r”赋值给了它,这就意味着第二次执行协程的时候,第一次的r很可能才被我们的入参"r"赋值,让我们验证一下:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)  --去掉local声明不影响结果,不是local的原因
     
    print("第二次协同程序执行输出", r)
end)
       
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co))
输出:
第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4
--分割线----
第二次协同程序执行输出	nil

在协程进入suspend之后,这回我们不传入参数,输出结果是nil而非4。
也就是说return coroutine.yield(2 * a)语句,并不会将yield内的内容返回给return,而且它们的执行顺序是先执行coroutine.yield后执行return。其流程是:coroutine.yield -> 等待resume->resume回传入参给yield ->return 执行,返回这个入参,传入多个参数验证:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r,v = foo(a + 1)
    print("r",r,"v",v)
end)
       
print(coroutine.resume(co, 1, 10)) -- true, 4
print(coroutine.resume(co,5,20))
输出:
第一次协同程序执行输出	1	10
foo 函数输出	2
true	4
r	5	v	20
true

因此,这意味着协程中的接收return...yield的变量,在yield之后其返回值实际上是我们resume再回传回去的,而回传的参数将被return接收,所以return...yield都需要我们手动进行定义/传参,现在我能理解之前的例子了:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)  --协程停止

    print("第二次协同程序执行输出", r)  --r需要传参
    r, s = coroutine.yield(a + b, a - b)  -- a,b的值为第一次调用协同程序时传入

    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入
end)
       
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9,"r"传入resume后的第一个变量r
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end,"x","y"分别传入r,s
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")

输出:
第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4
--分割线----
第二次协同程序执行输出	r
main	true	11	-9
---分割线---
第三次协同程序执行输出	x	y
main	true	10	结束协同程序
---分割线---
main	false	cannot resume dead coroutine
---分割线---

所以对应的,resume的第一次传入参数是作为协程开启的函数入参,而之后的resume的传入参数将被yield接收。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/54459.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

统一观测|借助 Prometheus 监控 ClickHouse 数据库

引言 ClickHouse 作为用于联机分析(OLAP)的列式数据库管理系统(DBMS), 最核心的特点是极致压缩率和极速查询性能。同时&#xff0c;ClickHouse 支持 SQL 查询&#xff0c;在基于大宽表的聚合分析查询场景下展现出优异的性能。因此&#xff0c;获得了广泛的应用。本文旨在分享阿…

CentOS7.3 安装 docker

亲测、截图 阿里云服务器 文章目录 更新源2345 启动开机自启 更新源 sudo yum update -y2 sudo yum install -y yum-utils device-mapper-persistent-data lvm23 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4 sudo yum …

计数排序算法

计数排序 计数排序说明&#xff1a; 计数排序&#xff08;Counting Sort&#xff09;是一种非比较性的排序算法&#xff0c;它通过统计元素出现的次数&#xff0c;然后根据元素出现的次数将元素排列在正确的位置上&#xff0c;从而实现排序。计数排序适用于非负整数或者具有确…

算法综合篇专题一:双指针问题

"就算没有看清那株灿烂的花蕊&#xff0c;也应该放声歌颂赞美鲜红的玫瑰" 1、移动零 (1) 题目解析 (2) 算法原理 class Solution { public:void moveZeroes(vector<int>& nums) {for(int cur0,dest-1;cur<nums.size();cur){if(nums[cu…

保姆级秋招教程之简历篇

大家好&#xff0c;我是千寻哥&#xff0c;个人简历在程序员求职过程中扮演着至关重要的角色。 今天我将详细给大家介绍一下写简历的必备要素和布局&#xff0c;同时强调应避免的“坑”&#xff01; 希望能通过这些技巧&#xff0c;能帮助程序员打造一份出色的简历&#xff0c;…

WEB:mfw

背景知识 Git泄露 Githack使用 命令执行漏洞 题目 这里页面里有Git&#xff0c;猜测是Git泄露 先用dirsearch扫一下 确实存在.git目录&#xff0c;可以尝试访问一下 使用Githack来下载并恢复.git文件 这里记得使用的时候关闭杀毒软件 结果会自动保存 点进去先看一下flag这个…

【前端知识】React 基础巩固(四十二)——React Hooks的介绍

React 基础巩固(四十二)——React Hooks的介绍 一、为什么需要Hook? Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下使用state以及其他的React特性&#xff08;比如生命周期&#xff09;。 class组件 VS 函数式组件&#xff1a; class的优势…

【机器学习】Feature scaling and Learning Rate (Multi-variable)

Feature scaling and Learning Rate 1、数据集2、学习率2.1 α \alpha α 9.9e-72.2 α \alpha α 9e-72.3 α \alpha α 1e-7 3、特征缩放3.1 特征缩放的原因3.2 Z-score 归一化3.3 预测3.4 损失等值线 导入所需的库 import numpy as np np.set_printoptions(precision…

为Win12做准备?微软Win11 23H2将集成AI助手:GPT4免费用

微软日前确认今年4季度推出Win11 23H2&#xff0c;这是Win11第二个年度更新。 Win11 23H2具体有哪些功能升级&#xff0c;现在还不好说&#xff0c;但它会集成微软的Copilot&#xff0c;它很容易让人想到多年前的“曲别针”助手&#xff0c;但这次是AI技术加持的&#xff0c;Co…

在k8s集群内搭建Prometheus监控平台

基本架构 Prometheus由SoundCloud发布&#xff0c;是一套由go语言开发的开源的监控&报警&时间序列数据库的组合。 Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态&#xff0c;任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的…

Linux上定位线上CPU飙高

【模拟场景】 写一个java main函数&#xff0c;死循环打印 System.out.println(“111111”) &#xff0c; 将其打成jar包放在linux中执行 1、通过TOP命令找到CPU耗用最厉害的那个进程的PID 2、top -H -p 进程PID 找到进程下的所有线程 可以看到 pid 为 94384的线程耗用cpu …

UM2080F32——32位SoC芯片

UM2080F32是基于ARM Cortex-M0内核的超低功耗、高性能的、单片集成(G)FSK/OOK无线收发机的32位SoC芯片。工作于200MHz~960MHz范围内&#xff0c;支持灵活可设的数据包格式&#xff0c;支持自动应答和自动重发功能&#xff0c;支持跳频操作&#xff0c;支持FEC功能&#xff0c;同…

BugKu CTF(杂项篇MISC)—社工-进阶收集

BugKu CTF(杂项篇MISC)—社工-进阶收集 提 示: flag{小美小区名字拼音} 描 述: 小明当年为了追求小美想尽办法获得小美的地址。直到有一天小美发了一条说说&#xff0c;小明觉得希望来了。(实战改编题&#xff0c;难度降低了。) [外链图片转存失败,源站可能有防盗链机制,建议…

yolov3-tiny原理解析及代码分析

前言 从去年十一月份开始学习yolo神经网络用于目标识别的硬件实现&#xff0c;到现在已经六个月了。一个硬件工程师&#xff0c;C/C基础都差劲的很&#xff0c;对照着darknet作者的源码和网上东拼西凑的原理讲解&#xff0c;一点一点地摸索。刚开始进度很慢&#xff0c;每天都…

pytorch学习——模型选择

一.概念 模型选择是机器学习中的重要环节&#xff0c;它涉及到从各种统计&#xff0c;机器学习或深度学习模型中选取最佳模型的过程。这涉及到许多关键概念&#xff0c;包括偏差与方差&#xff0c;过拟合与欠拟合&#xff0c;训练误差和泛化误差&#xff0c;交叉验证&#xff0…

【计算机网络】传输层协议 -- TCP协议

文章目录 1. TCP协议的引入2. TCP协议的特点3. TCP协议格式3.1 序号与确认序号3.2 发送缓冲区与接收缓冲区3.3 窗口大小3.4 六个标志位 4. 确认应答机制5. 超时重传机制6. 连接管理机制6.1 三次握手6.2 四次挥手 7. 流量控制8. 滑动窗口9. 拥塞控制10. 延迟应答11. 捎带应答12.…

Inkscape 1.3 版开放源代码 SVG 编辑器发布,新增形状生成器工具和许多更改

导读Inkscape 是功能强大的开源、跨平台、免费 SVG&#xff08;可缩放矢量图形&#xff09;编辑器&#xff0c;今天已更新到稳定的 1.3 版&#xff0c;这是一个引入新功能和许多改进的重要版本。 Inkscape 1.3 是在 Inkscape 1.2 发布一年零两个月后推出的&#xff0c;它引入了…

python-网络爬虫.regular

regular 正则表达式 (regular expression) 正则表达式(regular expression)描述了一种字符串匹配的模式 &#xff08;pattern&#xff09;&#xff0c; 可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串 中取出符合某个条件的子串等。 正则表达式是由普通…

学C的第三十一天【通讯录的实现】

相关代码gitee自取&#xff1a;C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 学C的第三十天【自定义类型&#xff1a;结构体、枚举、联合】_高高的胖子的博客-CSDN博客 通讯录需求&#xff1a; 实现一个通讯录&#xff0c; 通讯录中存放保存人的信息&#xff1…

SpringBoot中MongoDB的使用

SpringBoot中MongoDB的使用 MongoDB 是最早热门非关系数据库的之一&#xff0c;使用也比较普遍&#xff0c;一般会用做离线数据分析来使用&#xff0c;放到内网的居 多。由于很多公司使用了云服务&#xff0c;服务器默认都开放了外网地址&#xff0c;导致前一阵子大批 MongoD…