深入理解 `rt_completion` —— 一次性同步机制的正确使用姿势

【RT-Thread】深入理解 rt_completion —— 一次性同步机制的正确使用姿势

作者:shenyang
时间:2025年6月
标签:RT-Thread / 多线程同步 / 信号机制 / 嵌入式系统


一、前言

在 RT-Thread 系统中,rt_completion 是一种轻量级的线程同步机制,用于线程之间的一次性完成通知
很多开发者在刚接触它时,会误以为它与信号量 rt_sem 类似,可以重复使用、积累事件,这种误解往往导致程序出现“莫名其妙的死等”或“事件丢失”的问题。

本文将结合一个形象的**“步枪上弹→瞄准射击”的类比,彻底讲清楚 rt_completion使用场景、限制与正确姿势**。


二、rt_completion 是什么?

它的核心作用是:

用于某个线程等待另一个线程或中断完成一个特定操作的“一次性通知”。

它内部仅维护一个 flag 状态,一旦设置为完成(done),就只能消费一次,用完需重置。


三、形象比喻:步枪模式的同步机制

我们可以把 rt_completion 比作一把老式步枪:

步骤函数调用类比
上膛(准备完成量)rt_completion_init()向枪膛里装子弹
等待命中目标rt_completion_wait()扣动扳机、瞄准等待打中
任务完成通知rt_completion_done()子弹击中目标,完成通知线程 A

注意:如果你还没瞄准(没有 wait),却提前开枪(done)——这发子弹就白打了,永远不会击中任何目标!


四、使用场景与限制

推荐使用场景:

  • 线程 A 发起任务,等待线程 B 完成操作;
  • 操作顺序是确定的:先 wait,后 done;
  • 一次完成后,不再需要重复触发(或手动重新 init)。

例子:

rt_completion_t comp;
rt_completion_init(&comp);

// 线程 A
send_command();
rt_completion_wait(&comp, RT_WAITING_FOREVER);

// 线程 B
on_command_processed();
rt_completion_done(&comp);

不推荐使用的场景:

场景原因替代建议
done() 早于 wait()可能导致事件永远丢失rt_sem
需要多次触发 / 可重复通知completion 只支持一次完成rt_sem
多线程同时等待同一个事件无法同时唤醒多个线程rt_event
使用后没有手动 deinitinit重复使用将出错或行为不确定手动管理生命周期

五、与其他同步机制对比

同步机制是否可复用是否可积累适合场景
rt_completion❌ 否❌ 否一次性事件完成通知(先 wait 后 done)
rt_sem✅ 是✅ 是可重复的事件触发,线程唤醒
rt_event✅ 是✅ 是多状态事件组合、位图型触发
rt_mutex✅ 是❌ 否资源独占(临界区、串口等)

六、实际工程经验总结

在我们的项目中,有同事曾尝试这样使用 rt_completion

while (1) {
    rt_completion_init(&comp);
    // 等待外设响应
    rt_completion_wait(&comp, RT_WAITING_FOREVER);
    // 处理响应
}

而另一个线程可能在某些情况下先于 wait() 调用了 done(),结果就是:线程永远卡在 wait(),事件信号丢失!

✔️ 正确做法:使用 rt_sem 或者封装更安全的同步结构,确保事件不会提前丢失。


七、工程师建议文案(可用于注释或代码规范):

/* ⚠ 注意:
 * rt_completion 仅用于一次性事件同步。
 * 使用前必须 init,并确保 wait() 在 done() 之前调用。
 * 不适用于可重复、积累事件的同步需求。
 * 如果需求复杂,请使用 rt_sem 或 rt_event 替代。
 */

八、结语

rt_completion 是 RT-Thread 中非常轻巧的同步机制,但它只适合单次、顺序确定、使用简单的任务完成通知场合
在需要更复杂、可重入、可累积的场景中,请优先考虑使用 rt_semrt_event

希望你能把“步枪上弹→等待射击”这个类比记在脑中,用对武器,打中目标!

`rt_completion``rt_sem` 都是实时操作系统(RTOS)中用于同步的机制,但它们在功能和使用场景上存在一些不同。 ### 功能用途 - **rt_completion**:`rt_completion` 用于等待某个特定事件完成。当一个任务需要等待另一个任务完成某项工作时,可以使用 `rt_completion`。例如,一个任务启动了一个数据处理操作,另一个任务负责处理数据,当数据处理完成后,处理任务可以发出完成信号,等待的任务就会被唤醒继续执行。 ```c #include <rtthread.h> static struct rt_completion completion; static void data_process_thread(void *parameter) { // 模拟数据处理 rt_thread_mdelay(2000); // 发出完成信号 rt_completion_done(&completion); } static void wait_thread(void *parameter) { // 等待完成信号 rt_completion_wait(&completion, RT_WAITING_FOREVER); rt_kprintf("Data processing is completed.\n"); } ``` - **rt_sem**:`rt_sem` 是一种计数信号量,用于控制对共享资源的访问和任务间的同步。它可以表示可用资源的数量,任务在访问共享资源前需要获取信号量,使用完资源后释放信号量。例如,多个任务需要访问同一个串口设备,串口设备可以看作是一个共享资源,通过信号量来控制访问权限。 ```c #include <rtthread.h> static rt_sem_t sem; static void task1(void *parameter) { // 获取信号量 rt_sem_take(sem, RT_WAITING_FOREVER); // 访问共享资源 rt_kprintf("Task 1 is accessing the shared resource.\n"); // 释放信号量 rt_sem_release(sem); } static void task2(void *parameter) { // 获取信号量 rt_sem_take(sem, RT_WAITING_FOREVER); // 访问共享资源 rt_kprintf("Task 2 is accessing the shared resource.\n"); // 释放信号量 rt_sem_release(sem); } ``` ### 信号机制 - **rt_completion**:`rt_completion` 是一种一次性同步机制,它只有一个完成信号。一旦发出完成信号,等待的任务会被唤醒,之后再次等待时需要重新初始化。 - **rt_sem**:`rt_sem` 可以有多个计数,信号量的计数值表示可用资源的数量。任务可以多次获取和释放信号量,只要计数值不为零,任务就可以获取信号量。 ### 适用场景 - **rt_completion**:适用于一个任务等待另一个任务完成特定工作的场景,强调事件的完成。 - **rt_sem**:适用于多个任务对共享资源的访问控制和任务间的同步,强调资源的数量和访问权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值