太原做网站 小程序,可视化在线做网站,网站如何提交百度收录,公司建设网站的公司经常使用freertos进行开发的用户都会经常使用互斥锁的功能#xff0c;主要是用于实现原子操作#xff0c;避免冲突#xff0c;但是在合宙的luatos上#xff0c;其实没有相关的api接口#xff0c;lua本身也不支持这样操作#xff0c;于是本人研究了一下#xff0c;写出了…经常使用freertos进行开发的用户都会经常使用互斥锁的功能主要是用于实现原子操作避免冲突但是在合宙的luatos上其实没有相关的api接口lua本身也不支持这样操作于是本人研究了一下写出了一个库用于实现互斥锁的功能可能不太完善欢迎交流实现原理有人会说lua本身就是协程式的多仍任务要锁做什么是这样的确实也不需要但是复杂任务的时候比如串口抢占websocket抢占都涉及到先后操作和逻辑竞争这个时候就需要锁了而要实现互斥锁我们需要两个核心的apisys.waitUntil和sys.publishsys.waitUntil(msg, timeout)对应 RTOS 的Block (阻塞等待)。它会让当前协程挂起直到收到指定的消息或超时。sys.publish(msg)对应 RTOS 的Unblock (唤醒)。它会广播一个消息唤醒所有正在等待该消息的协程。我们可以在此基础上封装成类实现互斥锁实现代码-- lib_mutex.lualocalMutex{}Mutex.__indexMutex-- 创建一个新的互斥锁functionMutex.new()localo{lockedfalse,-- 锁状态ownernil,-- 持有者idtostring(os.time())..tostring(math.random())-- 生成唯一等待ID}returnsetmetatable(o,Mutex)end-- 获取锁 (类似 xSemaphoreTake)-- timeout: 超时时间(ms)默认为一直等待-- 返回值: true 成功获取, false 超时失败functionMutex:lock(timeout)localwait_result-- 如果锁已经被占用了就挂起当前协程等待iftimeoutandtype(timeout)numberthentimeoutmath.floor(timeout)endwhileself.lockeddo-- 当前协程会停在这里直到有人调用 unlock 触发消息或者超时wait_resultsys.waitUntil(self.id,timeout)-- 如果是因为超时醒来的 (wait_result 为 nil)则抢锁失败ifwait_resultnilthenreturnfalseend-- 如果是被唤醒的循环会继续再次检查 self.lockedend-- 成功抢到锁self.lockedtrueself.ownercoroutine.running()-- 记录是谁拿了锁returntrueend-- 释放锁 (类似 xSemaphoreGive)functionMutex:unlock()-- 只有锁的持有者才能释放锁 (防止误操作可选)ifself.owner~coroutine.running()thenreturnendifself.lockedthenself.lockedfalseself.ownernil-- 醒来后会重新进入 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 函数的循环中竞争资源。测试代码localMutexrequiremutex-- 创建一个全局锁保护“扬声器”资源localspeaker_lockMutex.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实现队列不也能执行你这个操作吗你这个操作的好处是什么用锁的话主要是更线性更符合直觉队列需要关注时序的问题还需要一个队列常驻后台实现轮询/等待唤起这样。