如何在单线程中通过信号量实现类上下文切换,完成Windows平台下基于Zephyr的nRF5340嵌入式固件单线程多实例仿真
当然有可行的方案!你要的本质就是单线程下的协作式多任务机制——完全能实现函数在信号量处暂停,等其他逻辑释放后再继续执行,还能轻松创建多个独立的设备实例。下面给你几个实用的实现思路和代码示例:
方案1:用C++20协程(最优雅的现代实现)
C++20原生支持的协程天生适配你的需求:它允许函数主动挂起,之后在任意时机恢复执行,状态由语言自动维护。你可以给每个设备实例封装成一个协程,配合自定义的用户态信号量实现同步。
第一步:实现可挂起的信号量
#include <coroutine> #include <queue> #include <atomic> class Semaphore { public: explicit Semaphore(int initial_count = 0) : count_(initial_count) {} // 定义等待信号量的可等待对象(协程挂起点) struct Awaiter { Semaphore& sem; // 检查信号量是否可用,可用则直接执行,不用挂起 bool await_ready() const noexcept { return sem.count_ > 0; } // 协程挂起时,把当前协程句柄加入等待队列 void await_suspend(std::coroutine_handle<> h) noexcept { sem.waiters_.push(h); } // 协程恢复时,消耗一个信号量计数 void await_resume() noexcept { --sem.count_; } }; // 供协程调用的等待接口 Awaiter wait() noexcept { return Awaiter{*this}; } // 释放信号量,唤醒一个等待的协程 void release() noexcept { ++count_; if (!waiters_.empty()) { auto h = waiters_.front(); waiters_.pop(); h.resume(); // 恢复协程执行 } } private: std::atomic<int> count_; std::queue<std::coroutine_handle<>> waiters_; };
第二步:封装设备实例的协程任务
#include <iostream> struct Device { Semaphore sem; int id; explicit Device(int device_id) : id(device_id) {} // 定义协程返回类型 struct Task { struct promise_type { Task get_return_object() noexcept { return {}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() noexcept {} void unhandled_exception() noexcept {} }; }; // 设备的主任务协程 Task run() { std::cout << "Device " << id << ": 启动任务,等待信号量\n"; co_await sem.wait(); // 在这里挂起,直到信号量被释放 std::cout << "Device " << id << ": 信号量已获取,继续执行后续逻辑\n"; // 这里可以添加原固件中的任务逻辑 } };
第三步:单线程调度多个设备实例
int main() { Device dev1(1); Device dev2(2); // 启动两个设备的任务(dev1、dev2会在wait处挂起) dev1.run(); dev2.run(); std::cout << "主线程:释放设备1的信号量\n"; dev1.sem.release(); // 唤醒dev1的协程 std::cout << "主线程:释放设备2的信号量\n"; dev2.sem.release(); // 唤醒dev2的协程 return 0; }
这个方案的优势是代码简洁、逻辑清晰,每个设备实例的状态由协程自动管理,全程在单线程内执行,没有操作系统级线程切换的开销。
方案2:手动实现状态机(兼容C++11及更早版本)
如果你的开发环境不支持C++20,手动状态机是兼容性最好的选择。核心思路是把设备的任务逻辑拆分成多个状态,每次调用只执行当前状态对应的代码片段,遇到信号量等待时保存状态并返回,下次调用再从该状态继续。
第一步:实现用户态信号量
#include <atomic> class Semaphore { public: explicit Semaphore(int initial_count = 0) : count_(initial_count) {} // 尝试获取信号量,返回是否成功 bool try_wait() noexcept { if (count_ > 0) { --count_; return true; } return false; } void release() noexcept { ++count_; } private: std::atomic<int> count_; };
第二步:封装带状态的设备实例
#include <iostream> #include <vector> enum class DeviceState { INIT, WAITING_FOR_SEM, RUNNING_AFTER_SEM, FINISHED }; class Device { public: explicit Device(int device_id) : id(device_id), state(DeviceState::INIT) {} // 每次调用执行一段逻辑,直到需要挂起或完成 void step() { switch (state) { case DeviceState::INIT: std::cout << "Device " << id << ": 启动任务,尝试获取信号量\n"; if (!sem.try_wait()) { state = DeviceState::WAITING_FOR_SEM; std::cout << "Device " << id << ": 信号量不可用,暂停执行\n"; return; } state = DeviceState::RUNNING_AFTER_SEM; // 状态流转,继续执行后续逻辑 case DeviceState::RUNNING_AFTER_SEM: std::cout << "Device " << id << ": 信号量已获取,执行任务逻辑\n"; // 这里添加原固件的任务代码 state = DeviceState::FINISHED; break; case DeviceState::WAITING_FOR_SEM: if (sem.try_wait()) { std::cout << "Device " << id << ": 信号量可用,恢复执行\n"; state = DeviceState::RUNNING_AFTER_SEM; } else { // 信号量仍不可用,直接返回,下次再尝试 return; } break; case DeviceState::FINISHED: return; } } Semaphore sem; int id; private: DeviceState state; };
第三步:单线程轮询调度
int main() { std::vector<Device> devices = {Device(1), Device(2)}; bool all_finished; do { all_finished = true; // 轮询每个设备的step函数,执行一段逻辑 for (auto& dev : devices) { if (dev.state != DeviceState::FINISHED) { dev.step(); all_finished = false; } } // 在这里处理信号量释放逻辑 devices[0].sem.release(); devices[1].sem.release(); } while (!all_finished); return 0; }
这个方案需要手动维护任务状态,代码稍显繁琐,但兼容性极强,不需要依赖任何C++20特性。
关键注意事项
- 协作式调度:所有任务的暂停和恢复都是主动触发的,不存在抢占式切换,必须确保每个任务的
step函数或协程不会无限执行,否则会阻塞整个线程。 - 实例隔离:每个设备实例必须拥有独立的信号量和状态(协程句柄或状态变量),确保多个实例之间互不干扰。
- 用户态同步:不需要使用操作系统提供的线程同步原语(比如
std::semaphore),因为全程在单线程内执行,用户态实现足够安全高效。
内容的提问来源于stack exchange,提问作者LeonMoto
相关产品推荐
相关产品推荐

