Lua协同程序(线程)

1、 Lua 的协同程序(coroutine)简单介绍

        Lua 的协同程序(coroutine)是一种轻量级的线程,允许你在多个任务之间进行协作式多任务处理。与操作系统线程不同,协同程序是由程序员显式控制的,不会自动切换,而是通过 yield 和 resume 来手动切换执行权。

        协同程序的核心思想是协作式多任务,即一个任务主动让出执行权,另一个任务才能继续执行。这种机制非常适合需要分步执行的任务,例如状态机、迭代器、异步任务等。

  1. 创建协同程序:

    • 使用 coroutine.create(f) 创建一个协同程序,其中 f 是一个函数。

    • 返回的是一个协同程序对象(类型为 thread)。如果看过我之前的文章,其实这个有点像pthread_create,即创建一个线程,但是两个是不一样的东西

  2. 启动或恢复协同程序:

    • 使用 coroutine.resume(co, ...) 启动或恢复一个协同程序。

    • 可以传递参数给协同程序。

  3. 让出执行权:

    • 在协同程序内部,使用 coroutine.yield(...) 让出执行权,并返回一些值给调用者。

  4. 检查协同程序状态:

    • 使用 coroutine.status(co) 检查协同程序的状态:

      • "running":正在运行。

      • "suspended":暂停(等待恢复)。

      • "dead":已经结束。

  5. 获取当前运行的协同程序:

    • 使用 coroutine.running() 获取当前正在运行的协同程序。

 2、lua的协同程序和pthread的区别

    pthread_create 和 Lua 的协同程序(coroutine)是两种完全不同的并发机制,它们的实现方式、使用场景和行为都有显著区别。

1. pthread_create

pthread_create 是 POSIX 线程(pthread)库中的一个函数,用于创建操作系统级别的线程。

  • 操作系统线程:pthread_create 创建的是真正的操作系统线程,由操作系统调度。线程是抢占式的,操作系统会在任意时刻切换线程的执行。

  • 并发性:多个线程可以并行运行(如果有多核 CPU)。线程之间是真正并发的。

  • 资源开销:线程的创建和切换需要较大的资源开销(内存、上下文切换等)。线程数量受操作系统限制。

  • 同步和通信:线程之间需要通过锁(如 pthread_mutex)、条件变量( pthread_cond)等机制进行同步和通信。容易出现竞态条件(race condition)和死锁(deadlock)。

  • 使用场景:适合需要真正并行执行的场景,例如 CPU 密集型任务或需要利用多核性能的任务。

2. Lua 协同程序(coroutine)

Lua 的协同程序是一种用户态的轻量级线程:

  • 用户态线程:

    • 协同程序是由 Lua 虚拟机管理的,不依赖操作系统线程。

    • 协同程序是协作式的,需要显式调用 yield 和 resume 来切换任务。

  • 并发性:

    • 协同程序是单线程的,同一时刻只有一个协同程序在运行。

    • 无法利用多核 CPU 实现真正的并行。

  • 资源开销:

    • 协同程序的创建和切换开销非常小,适合高并发场景。

    • 可以创建成千上万个协同程序。

  • 同步和通信:

    • 协同程序之间不需要锁或条件变量,因为它们是协作式的。

    • 数据共享更简单,不容易出现竞态条件。

  • 使用场景:

    • 适合 I/O 密集型任务、状态机、迭代器等需要分步执行的场景。

    • 不适合 CPU 密集型任务。

3、协同程序的相关函数解析

1. coroutine.create(f)
  • 功能: 创建一个新的协同程序。

  • 参数:f: 一个函数,作为协同程序的主体。

  • 返回值:返回一个协同程序对象(类型为 thread)。

  • 2. coroutine.resume(co, ...)
  • 功能: 启动或恢复一个协同程序的执行。

  • 参数:co: 协同程序对象(由 coroutine.create 创建)。...: 可选参数,传递给协同程序的参数。

  • 返回值:第一个返回值是一个布尔值,表示协同程序是否成功执行。后续返回值是协同程序通过 coroutine.yield 或 return 返回的值。

  • 3.coroutine.yield(...)
  • 功能: 暂停当前协同程序的执行,并返回一些值给调用者。

  • 参数:...: 可选参数,作为 yield 的返回值。

  • 返回值:无返回值(因为 yield 会暂停执行)。

  • 注意:只能在协同程序内部调用。

4.coroutine.status(co)
  • 功能: 获取协同程序的当前状态。

  • 参数:co: 协同程序对象。

  • 返回值:返回一个字符串,表示协同程序的状态:

    • "dead": 已经结束。

    • "suspended": 暂停(等待恢复)。

    • "running": 正在运行。

 5.coroutine.running()
  • 功能: 获取当前正在运行的协同程序。

  • 参数:无。

  • 返回值:返回当前正在运行的协同程序对象。如果当前不在协同程序中,返回 nil

6.coroutine.wrap(f)
  • 功能: 创建一个新的协同程序,并返回一个函数,调用该函数会恢复协同程序的执行。

  • 参数:f: 一个函数,作为协同程序的主体。

  • 返回值:返回一个函数,调用该函数相当于调用 coroutine.resume

  • 注意:

    • 与 coroutine.create 不同,coroutine.wrap 返回的函数会直接返回 yield 的值,而不是布尔状态。

 看下面这个代码,会输出什么,如果按照c语言当中的语法规则,这里可能会直接输出开始执行,然后停止,但是这是在lua语法规则当中,所以对于这里来说,会输出nil,nil,也就是两个空,那么为什么会这样呢,Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。这里我们只是定义了一个函数,函数 foo 中的输出没有执行,是因为 Lua 的协同程序是惰性执行的。也就是说,协同程序不会自动执行,必须通过 coroutine.resume 显式地启动或恢复它。:

function foo()print("开始执行")local value=coroutine.yield("暂停执行")print("协同恢复执行")print("结束执行")
end
--local co=coroutine.create(foo)--local status,result=coroutine.resume(co)
print(result)--status,result=coroutine.resume(co,42)
print(result)

 如果将注释去掉,这样之后就能够执行代码了,这时候协同程序被创建

function foo()print("开始执行")local value=coroutine.yield("暂停执行")print("协同恢复执行,传入"..tostring(value))print("结束执行")
end
local co=coroutine.create(foo)local status,result=coroutine.resume(co)
print(status,result)status,result=coroutine.resume(co,42)
print(status,result)

local status, result = coroutine.resume(co)
print(status,result)  -- 输出: true,暂停执行

-- 恢复协同程序,并传入一个值
status, result = coroutine.resume(co, 42)

这三行代码如何理解呢?看resume这个函数,这个函数被调用了两次,第一次用来启动协同程序,而且resume这个代码来说有两个返回值,第一个返回值给到status,如果执行成功,返回true,第二个返回值给到result,返回的是暂停执行。那么问题来了,为什么返回值是yield函数的参数呢,这是因为,协同程序是一个特殊的线程,也就是单线程,一次只能运行一个线程,不是并行执行,如果是操作系统里面的线程执行,会跳转到线程当中执行,并且当前代码也会继续执行,但是在lua当中,线程执行只能有一个,而这里会在foo函数当中继续执行,直到遇到暂停函数,这时候会暂停线程,回来执行下面的代码,也就是print函数,这时候打印出来的值就是yield函数的参数,第二次使用resume是用来恢复协同程序,因为上面的协同程序被暂停了,这时候会再次恢复协同程序,但是不会在刚才已经执行过的代码继续执行,而是执行已经执行过的下一行代码。那么如果这时候再有一个resume函数呢?看下面的代码:

function foo()print("开始执行")local value=coroutine.yield("暂停执行")print("协同恢复执行,传入"..tostring(value))print("结束执行")
end
local co=coroutine.create(foo)local status,result=coroutine.resume(co)
print(status,result)status,result=coroutine.resume(co,42)
print(status,result)
status,result=coroutine.resume(co,42)
print(result)

 这段代码当中拥有三个resume函数,我们知道,第一和第二个是用来启动和恢复协同线程的,这时候第三个会受到影响吗?

直接看输出结果:

开始执行
true	暂停执行
协同恢复执行,传入42
结束执行
true	nil
cannot resume dead coroutine

这时候的输出结果出现了一句cannot resume dead corutine 也就是说,cannot resume dead coroutine 是 Lua 中的一个错误提示,表示你尝试恢复一个已经**死亡(dead)**的协同程序。协同程序一旦执行完毕(即函数返回或执行到末尾),就会进入 dead 状态,此时再调用 coroutine.resume 就会触发这个错误。这是一个错误提示。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

流媒体学习

流媒体 特性流媒体流协议FLVMSE定义采用数据流方式在网络上进行播放的媒体格式用于传输流媒体数据的网络协议Flash Video&#xff0c;一种流媒体格式一项 W3C 规范&#xff0c;允许 JavaScript 为 <audio> 和 <video> 动态构造媒体源作用提供连续的音视频播放体验&…

【简博士统计学习方法】第1章:3. 统计学习方法的三要素

3. 统计学习方法的三要素 3.1 监督学习的三要素 3.1.1 模型 假设空间&#xff08;Hypothesis Space&#xff09;&#xff1a;所有可能的条件概率分布或决策函数&#xff0c;用 F \mathcal{F} F表示。 若定义为决策函数的集合&#xff1a; F { f ∣ Y f ( X ) } \mathcal{F…

Linux内核 -- RTC 驱动的注册方式

Linux 内核中 RTC 驱动的注册方式 在 Linux 内核中&#xff0c;RTC&#xff08;Real-Time Clock&#xff09;驱动的注册可以通过多种方式实现&#xff0c;以下整理了常见的注册方式及其注意事项。 1. 使用 devm_rtc_device_register 这是注册 RTC 驱动的最常用方法&#xff…

JavaEE初阶——计算机工作原理

一、什么是JavaEE JavaEE&#xff08;Java Platform&#xff0c;Enterprise Edition&#xff09;是sun公司&#xff08;2009年4月20日甲骨文将其收购&#xff09;推出的企业级应用程序版本。这个版本以前称为 J2EE。能够帮助我们开发和部署可移植、健壮、可伸缩且安全的服务器…

多目标优化算法——基于聚类的不规则Pareto前沿多目标优化自适应进化算法(CA-MOEA)

基于聚类的不规则Pareto前沿多目标优化自适应进化算法&#xff08;CA-MOEA&#xff09; 一、算法简介 简介&#xff1a; 现有的多目标进化算法&#xff08;moea&#xff09;在具有规则Pareto前沿且Pareto最优解在目标空间上连续分布的多目标优化问题&#xff08;MOPs&#xff…

短诗《腊八粥》

《腊八粥》现•佚名已买花生同煮粥&#xff0c;粥不似&#xff0c;少年稠也不复&#xff0c;少年仇亦不赴&#xff0c;少年愁终不负&#xff0c;少年筹 &#xff08;主编目前所有分类&#xff1a; 身&#xff0c;心&#xff0c;灵思 工作&#xff0c;生活 创作 剧 让世界更…

封装深拷贝方法

前言 在今年的四月份我写了一篇有关深拷贝的博客文章 我与深拷贝_radash 深拷贝-CSDN博客。在该文章中有一个令我感到遗憾的点就是我没有实现一个自己手写的深拷贝。如今我想弥补当初的遗憾&#xff0c;在这篇文章中详细的讲述一下如何手写一个深拷贝方法。 lodash中是如何实…

数据结构:树

数据结构中的树 树&#xff08;Tree&#xff09;是一种非线性数据结构&#xff0c;用于表示具有层次结构的数据。树结构由节点&#xff08;Node&#xff09;和边&#xff08;Edge&#xff09;组成&#xff0c;节点之间通过边连接&#xff0c;形成父子关系。树是一种抽象数据类…

【信息系统项目管理师】第15章:项目风险管理过程详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划风险管理1、输入2、工具与技术3、输出二、识别风险1、输入2、工具与技术3、输出三、实施定性风险分析1、输入2、工具与技术3、输出四、实施定量风险分析1、输入2、工具与技术3、输出五、规划风险应对1、…

flutter web 路由问题

开发问题背景&#xff1a; flutte 项目中开发网页暴露出来供外部的 网页调用&#xff0c;并不是跳转到项目的首页 项目中使用的路由是 GetX 4.6.0 存在的问题 跳回到首页的问题 web -> flutterflutter 使用 history.back 到web&#xff0c;web forward 到 flutter 此时…

在不到 5 分钟的时间内将威胁情报 PDF 添加为 AI 助手的自定义知识

作者&#xff1a;来自 Elastic jamesspi 安全运营团队通常会维护威胁情报报告的存储库&#xff0c;这些报告包含由报告提供商生成的大量知识。然而&#xff0c;挑战在于&#xff0c;这些报告的内容通常以 PDF 格式存在&#xff0c;使得在处理安全事件或调查时难以检索和引用相关…

二、模型训练与优化遇到的问题1:python=3.10:指定 Python 版本为 3.10。这里为什么指定版本为3.10,有什么依据

目录 一、Python 版本选择的依据 1. TensorFlow 和 Keras 的兼容性 2. Python 3.10 的优势 二、如何选择适合的 Python 版本 1. 检查 TensorFlow 的官方兼容性文档 2. 选择受支持且稳定的版本 3. 避免使用过于旧或过新的版本 三、如何在 Anaconda 中选择不同的 Python …

探索 Vue.js 的动态样式与交互:一个有趣的样式调整应用

修改日期备注2025.1.3初版 一、前言 今天和大家分享在 Vue.js 学习过程中开发的超酷的小应用。这个应用可以让我们通过一些简单的交互元素&#xff0c;如复选框、下拉菜单和输入框&#xff0c;来动态地改变页面上元素的样式哦 让我们一起深入了解一下这个项目的实现过程&…

css出现边框

前言 正常情况下&#xff0c;开启 contenteditable 属性后会出现 “黑色边框”。 如下图所示&#xff0c;很影响美观&#xff1a; 您可能想去掉它&#xff0c;就像下面这样&#xff1a; 解决方案 通过选择器&#xff0c;将 focus 聚焦时移除 outline 属性即可。 如下代码所示&a…

恋爱脑学编程之C++模板编程大冒险

一、模板编程初相识&#xff1a;开启泛型编程魔法之旅 嘿&#xff0c;小伙伴们&#xff01;今天咱们要一起探索C中超级厉害的模板编程。它就像是一把万能钥匙&#xff0c;可以打开各种类型数据的大门&#xff0c;让咱们写出超酷的与类型无关的代码&#xff0c;大大提高代码的复…

ubuntu 20.04 安装 5.4 内核

最近需要用linux 5.4内核&#xff08;发现它和5.14 在Block层有些差异&#xff09;&#xff0c;以对比和5.14内核的差异。目前已安装的虚拟机&#xff0c;Centos8的默认内核是4.18&#xff0c;Redhat9.1的内核是5.14&#xff0c;Ubuntu20.04的内核是5.15&#xff0c;故在ubuntu…

enzymejest TDD与BDD开发实战

一、前端自动化测试需要测什么 1. 函数的执行逻辑&#xff0c;对于给定的输入&#xff0c;输出是否符合预期。 2. 用户行为的响应逻辑。 - 对于单元测试而言&#xff0c;测试粒度较细&#xff0c;需要测试内部状态的变更与相应函数是否成功被调用。 - 对于集成测试而言&a…

netty解码器LengthFieldBasedFrameDecoder用法详解

Netty Netty是一个高性能、异步事件驱动的网络应用程序框架,它提供了对并发和异步编程的抽象,使得开发网络应用程序变得更加简单和高效。 在Netty中,EventLoopGroup是处理I/O操作的多线程事件循环器。在上面的示例中,我们创建了两个EventLoopGroup实例:bossGroup和worker…

继承(5)

大家好&#xff0c;今天我们继续来学习继承的相关知识&#xff0c;来看看子类构造方法&#xff08;也叫做构造器&#xff09;是如何做的。 1.6 子类构造方法 父子父子,先有父再有子,即:子类对象构选时,需要先调用基类构造方法,然后执行子类的构造方法 ★此时虽然执行了父类的…

Spring Boot 依赖配置分离多种打包方式

生产上发布 Spring Boot 项目时,但凡代码有一丁点改动,就得把整个项目包括依赖重新打包上传部署,这样的包很大,影响效率 为解决这个问题,可以把依赖(pom中的依赖jar包)、配置文件(resources 下的 applacation.yml 等文件)从项目主体里剥离出来,后续部署时,只需发布代…