news 2026/4/16 12:46:11

在luatos中实现互斥锁的功能-以合宙luatos和air780EGH为实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在luatos中实现互斥锁的功能-以合宙luatos和air780EGH为实现

经常使用freertos进行开发的用户都会经常使用互斥锁的功能,主要是用于实现原子操作,避免冲突,但是在合宙的luatos上,其实没有相关的api接口,lua本身也不支持这样操作,于是本人研究了一下,写出了一个库用于实现互斥锁的功能,可能不太完善,欢迎交流

实现原理

有人会说,lua本身就是协程式的多仍任务,要锁做什么,是这样的,确实也不需要,但是复杂任务的时候,比如串口抢占,websocket抢占,都涉及到先后操作和逻辑竞争,这个时候就需要锁了,而要实现互斥锁,我们需要两个核心的apisys.waitUntilsys.publish


  • sys.waitUntil(msg, timeout):对应 RTOS 的Block (阻塞等待)。它会让当前协程挂起,直到收到指定的消息或超时。
  • sys.publish(msg):对应 RTOS 的Unblock (唤醒)。它会广播一个消息,唤醒所有正在等待该消息的协程。

我们可以在此基础上,封装成类实现互斥锁

实现代码

-- lib_mutex.lualocalMutex={}Mutex.__index=Mutex-- 创建一个新的互斥锁functionMutex.new()localo={locked=false,-- 锁状态owner=nil,-- 持有者id=tostring(os.time())..tostring(math.random())-- 生成唯一等待ID}returnsetmetatable(o,Mutex)end-- 获取锁 (类似 xSemaphoreTake)-- timeout: 超时时间(ms),默认为一直等待-- 返回值: true 成功获取, false 超时失败functionMutex:lock(timeout)localwait_result-- 如果锁已经被占用了,就挂起当前协程等待iftimeoutandtype(timeout)=="number"thentimeout=math.floor(timeout)endwhileself.lockeddo-- 当前协程会停在这里,直到有人调用 unlock 触发消息,或者超时wait_result=sys.waitUntil(self.id,timeout)-- 如果是因为超时醒来的 (wait_result 为 nil),则抢锁失败ifwait_result==nilthenreturnfalseend-- 如果是被唤醒的,循环会继续,再次检查 self.lockedend-- 成功抢到锁self.locked=trueself.owner=coroutine.running()-- 记录是谁拿了锁returntrueend-- 释放锁 (类似 xSemaphoreGive)functionMutex:unlock()-- 只有锁的持有者才能释放锁 (防止误操作,可选)ifself.owner~=coroutine.running()thenreturnendifself.lockedthenself.locked=falseself.owner=nil-- 醒来后会重新进入 while 循环抢锁,由于 Lua 是单线程,必定有一个会先抢到,其他的继续睡sys.publish(self.id)endendreturnMutex

代码解释(很罗嗦可跳过)

首先看数据结构的设计,在 Mutex.new 构造函数中,我们定义了锁的三个元数据:locked 状态位用于原子性地标记资源是否被占用;owner 用于记录持有当前锁的协程句柄,这至关重要,它确保了“谁加锁谁解锁”的安全原则,防止其他任务意外释放不属于自己的锁;而 id 则是一个由时间戳和随机数生成的唯一字符串,它作为消息通道的唯一标识符,保证了系统内不同锁对象之间的事件广播互不干扰。
接下来是至关重要的上锁逻辑 Mutex:lock。该函数首先对传入的超时参数进行了整数化处理(math.floor),这是为了防止浮点数导致底层定时器崩溃。紧接着进入核心的竞争循环,这里使用 while self.locked 循环而不是简单的 if 判断,是因为在多任务环境下,当锁被释放并广播消息时,可能有多个等待中的协程同时被唤醒,但由于 Lua 是单线程执行的,它们只能依次运行。假设任务 A 和 B 同时被唤醒,A 先运行发现锁空闲并抢占了它,当 B 随后运行时,必须能再次检测到锁已被 A 抢占(locked 变回 true),从而再次进入 sys.waitUntil 挂起等待。当 waitUntil 返回代表超时的结果时直接返回 false,避免任务无限期死等。一旦成功跳出循环,函数立即标记 locked 为 true 并记录当前协程为 owner,由于 Lua 协程的非抢占特性,这两步操作中间不会被打断,从而保证了加锁过程的原子性。
最后是解锁逻辑 Mutex:unlock。为了保证业务逻辑的安全性,代码首先通过 coroutine.running() 校验当前协程是否为锁的持有者,有点类似递归锁或二值信号量的所有权保护,防止非持有者误操作破坏业务逻辑。校验通过后,将 locked 状态重置为 false 并清空 owner,随即调用 sys.publish(self.id) 广播消息。这一步是整个机制的触发器,它通知 LuatOS 调度器查找所有正在该 id 上阻塞等待的协程,将它们的状态从“挂起”转为“就绪”,使它们在下一个调度周期有机会重新进入 lock 函数的循环中竞争资源。

测试代码

localMutex=require"mutex"-- 创建一个全局锁,保护“扬声器”资源localspeaker_lock=Mutex.new()-- 模拟 TTS 播放函数localfunctionspeak(text)log.info("TTS","准备播放:",text)-- 尝试拿锁,等待时间无限ifspeaker_lock:lock()thenlog.info("LOCK",text,"拿到锁了,开始播放...")-- 模拟播放过程耗时 2秒sys.wait(2000)log.info("TTS","播放结束:",text)-- 释放锁speaker_lock:unlock()log.info("LOCK","锁已释放")elselog.info("LOCK","获取锁超时/失败")endendlog.info("TTS","拿锁比赛现在开始")-- 任务 A:每 3 秒想说话sys.taskInit(function()whiletruedospeak("我是任务 A")sys.wait(3000)endend)-- 任务 B:每 1 秒想说话sys.taskInit(function()sys.wait(500)whiletruedospeak("我是任务 B")sys.wait(1000)endend)sys.run()

下面式运行日志

[2025-12-17 17:57:32.702][000000000.232] D/main loadlibs psram 3211632 104972 106200 [2025-12-17 17:57:32.705][000000000.251] I/user.main Air780EGH_gnss 1.0.0 [2025-12-17 17:57:32.708][000000000.252] I/user.main Air780EGH_gnss [2025-12-17 17:57:32.711][000000000.480] I/user.TTS 拿锁比赛现在开始 [2025-12-17 17:57:32.713][000000000.481] I/user.TTS 准备播放: 我是任务 A [2025-12-17 17:57:32.716][000000000.481] I/user.LOCK 我是任务 A 拿到锁了,开始播放... [2025-12-17 17:57:32.898][000000000.992] I/user.TTS 准备播放: 我是任务 B [2025-12-17 17:57:33.944][000000002.006] D/mobile cid1, state0 [2025-12-17 17:57:33.950][000000002.006] D/mobile bearer act 0, result 0 [2025-12-17 17:57:33.953][000000002.007] D/mobile NETIF_LINK_ON -> IP_READY [2025-12-17 17:57:33.970][000000002.063] D/mobile TIME_SYNC 0 [2025-12-17 17:57:34.266][000000002.358] soc_cms_proc 2219:cenc report 1,51,1,15 [2025-12-17 17:57:34.376][000000002.463] D/mobile NETIF_LINK_ON -> IP_READY [2025-12-17 17:57:34.390][000000002.481] I/user.TTS 播放结束: 我是任务 A [2025-12-17 17:57:34.393][000000002.482] I/user.LOCK 锁已释放 [2025-12-17 17:57:34.395][000000002.483] I/user.LOCK 我是任务 B 拿到锁了,开始播放... [2025-12-17 17:57:35.526][000000003.628] D/mobile ims reg state 0 [2025-12-17 17:57:35.529][000000003.629] D/mobile LUAT_MOBILE_EVENT_CC status 0 [2025-12-17 17:57:35.531][000000003.629] D/mobile LUAT_MOBILE_CC_READY [2025-12-17 17:57:36.383][000000004.483] I/user.TTS 播放结束: 我是任务 B [2025-12-17 17:57:36.385][000000004.484] I/user.LOCK 锁已释放 [2025-12-17 17:57:37.394][000000005.482] I/user.TTS 准备播放: 我是任务 A [2025-12-17 17:57:37.397][000000005.483] I/user.LOCK 我是任务 A 拿到锁了,开始播放... [2025-12-17 17:57:37.398][000000005.484] I/user.TTS 准备播放: 我是任务 B [2025-12-17 17:57:39.385][000000007.483] I/user.TTS 播放结束: 我是任务 A [2025-12-17 17:57:39.388][000000007.484] I/user.LOCK 锁已释放 [2025-12-17 17:57:39.391][000000007.485] I/user.LOCK 我是任务 B 拿到锁了,开始播放... [2025-12-17 17:57:41.390][000000009.485] I/user.TTS 播放结束: 我是任务 B [2025-12-17 17:57:41.394][000000009.486] I/user.LOCK 锁已释放 [2025-12-17 17:57:42.389][000000010.484] I/user.TTS 准备播放: 我是任务 A [2025-12-17 17:57:42.393][000000010.485] I/user.LOCK 我是任务 A 拿到锁了,开始播放... [2025-12-17 17:57:42.395][000000010.486] I/user.TTS 准备播放: 我是任务 B [2025-12-17 17:57:44.384][000000012.485] I/user.TTS 播放结束: 我是任务 A [2025-12-17 17:57:44.387][000000012.486] I/user.LOCK 锁已释放 [2025-12-17 17:57:44.390][000000012.487] I/user.LOCK 我是任务 B 拿到锁了,开始播放... [2025-12-17 17:57:46.387][000000014.487] I/user.TTS 播放结束: 我是任务 B [2025-12-17 17:57:46.390][000000014.488] I/user.LOCK 锁已释放 [2025-12-17 17:57:47.384][000000015.486] I/user.TTS 准备播放: 我是任务 A [2025-12-17 17:57:47.387][000000015.487] I/user.LOCK 我是任务 A 拿到锁了,开始播放... [2025-12-17 17:57:47.389][000000015.488] I/user.TTS 准备播放: 我是任务 B [2025-12-17 17:57:49.400][000000017.487] I/user.TTS 播放结束: 我是任务 A [2025-12-17 17:57:49.403][000000017.488] I/user.LOCK 锁已释放 [2025-12-17 17:57:49.404][000000017.489] I/user.LOCK 我是任务 B 拿到锁了,开始播放... [2025-12-17 17:57:51.402][000000019.489] I/user.TTS 播放结束: 我是任务 B [2025-12-17 17:57:51.405][000000019.490] I/user.LOCK 锁已释放 [2025-12-17 17:57:52.399][000000020.488] I/user.TTS 准备播放: 我是任务 A [2025-12-17 17:57:52.405][000000020.489] I/user.LOCK 我是任务 A 拿到锁了,开始播放... [2025-12-17 17:57:52.407][000000020.490] I/user.TTS 准备播放: 我是任务 B [2025-12-17 17:57:54.390][000000022.489] I/user.TTS 播放结束: 我是任务 A [2025-12-17 17:57:54.393][000000022.490] I/user.LOCK 锁已释放 [2025-12-17 17:57:54.396][000000022.491] I/user.LOCK 我是任务 B 拿到锁了,开始播放... [2025-12-17 17:57:56.390][000000024.491] I/user.TTS 播放结束: 我是任务 B [2025-12-17 17:57:56.394][000000024.492] I/user.LOCK 锁已释放

分析每个任务持有的时长,可以看到锁是成功起效了,并没有出现ab同时抢任务的问题


以上就是我的设计,虽然大家一般用lua都不会开发很复杂的程序,但是凡是都有个例外,此外,还有人可能会说,那我用table实现队列,不也能执行你这个操作吗,你这个操作的好处是什么?用锁的话主要是更线性更符合直觉,队列需要关注时序的问题,还需要一个队列常驻后台实现轮询/等待唤起这样。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:58:14

2025爬虫技术前沿:AI驱动、多模态与反反爬的军备竞赛

2025年,网络爬虫技术已深度融入AI时代。根据最新行业报告(如PromptCloud和Apify的2025年报告),全球web scraping市场规模已超10亿美元,年增长率达双位数。AI爬虫流量占比显著上升,反爬机制也更智能化&#…

作者头像 李华
网站建设 2026/4/16 11:03:34

基于Spring Boot的高校教师资源管理系统

基于Spring Boot的高校教师资源管理系统介绍 基于Spring Boot的高校教师资源管理系统是一款集成了现代信息技术的高效工具,旨在优化高校教师资源的管理和配置,提升教学管理水平和教育质量。以下是对该系统的详细介绍: 一、系统定位与用户需求…

作者头像 李华
网站建设 2026/4/16 11:03:11

选对语言,赢在起点!新手 C、Java、Python 指南uT#45篇

SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。目录安装SQLAlchemy核心概念连接数据库定义数据模型创建数据库表基本CRUD操作查询数据关系操…

作者头像 李华
网站建设 2026/4/16 10:40:10

C++课后习题训练记录Day52

1.练习项目&#xff1a; 练习使用map函数及其常用函数 2.选择课程 在蓝桥云课中选择课程《16届蓝桥杯省赛无忧班&#xff08;C&C 组&#xff09;4期》&#xff0c;选择第STL”课程16并开始练习。 3.开始练习 &#xff08;1&#xff09;源码&#xff1a; #include<…

作者头像 李华
网站建设 2026/4/16 9:23:31

FastAPI 流式响应中,如何优雅处理客户端断连后的数据库操作?

问题出现过程1. 客户端发起流式对话请求我们从一个典型的流式对话接口开始。我们使用依赖注入来获取一个 SQLAlchemy 的 AsyncSession&#xff0c;在对话开始时创建消息&#xff0c;在对话结束后更新 AI 的回答。流式对话原始代码&#xff08;伪代码&#xff09;2. 客户端取消对…

作者头像 李华