RT-Thread线程间同步与通信 — 多线程协作的核心
前言
在多线程系统中,线程之间需要"协调工作"和"交换信息"。RT-Thread提供了信号量、互斥量、事件集三种同步机制,以及邮箱、消息队列、信号三种通信机制。本文将逐一介绍它们的原理、API和实战用法。
第一部分:线程间同步
多个线程同时访问共享资源可能导致数据错乱,同步机制保证线程有序协作。
一、信号量(Semaphore)
信号量可以理解为电影院的座位数。当座位满了,新观众(线程)必须等待前面的人离开(释放资源)。
1.1 工作机制
- 获取信号量(P操作):信号量值 > 0 时获取成功,值减1;值为0时线程等待。
- 释放信号量(V操作):信号量值加1,唤醒等待的线程。
1.2 核心API
| rt_sem_create(name, value, flag) | 动态创建信号量 |
| rt_sem_init(sem, name, value, flag) | 静态初始化信号量 |
| rt_sem_take(sem, time) | 获取信号量(可设超时) |
| rt_sem_release(sem) | 释放信号量 |
| rt_sem_delete(sem) | 删除动态信号量 |
flag 选项:
- RT_IPC_FLAG_PRIO:按优先级排队等待
- RT_IPC_FLAG_FIFO:按先进先出排队等待
1.3 典型应用场景
场景一:任务同步 — 线程1等待线程2完成后再执行
#include <rtthread.h>
static rt_sem_t sync_sem = RT_NULL;
static void thread1_entry(void *parameter)
{
rt_kprintf("线程1:等待线程2发送信号…\\n");
rt_sem_take(sync_sem, RT_WAITING_FOREVER);
rt_kprintf("线程1:收到信号,继续执行\\n");
}
static void thread2_entry(void *parameter)
{
rt_kprintf("线程2:执行操作…\\n");
rt_thread_mdelay(2000);
rt_kprintf("线程2:操作完成,发送信号\\n");
rt_sem_release(sync_sem);
}
int main(void)
{
sync_sem = rt_sem_create("sync_sem", 0, RT_IPC_FLAG_PRIO);
rt_thread_t tid1 = rt_thread_create("t1", thread1_entry, RT_NULL, 512, 10, 5);
rt_thread_t tid2 = rt_thread_create("t2", thread2_entry, RT_NULL, 512, 10, 5);
if (tid1 != RT_NULL) rt_thread_startup(tid1);
if (tid2 != RT_NULL) rt_thread_startup(tid2);
return 0;
}
关键:初始值为0,线程1调用 rt_sem_take 会阻塞,直到线程2调用 rt_sem_release。
场景二:资源互斥 — 保护共享资源
static rt_sem_t mutex_sem = RT_NULL;
static int shared_resource = 0;
static void thread_entry(void *parameter)
{
while (1)
{
rt_sem_take(mutex_sem, RT_WAITING_FOREVER);
shared_resource++;
rt_kprintf("%s: shared = %d\\n", (char *)parameter, shared_resource);
rt_thread_mdelay(1000);
rt_sem_release(mutex_sem);
rt_thread_mdelay(500);
}
}
int main(void)
{
mutex_sem = rt_sem_create("mutex_sem", 1, RT_IPC_FLAG_PRIO); // 初始值为1
rt_thread_t t1 = rt_thread_create("t1", thread_entry, "Thread1", 512, 10, 5);
rt_thread_t t2 = rt_thread_create("t2", thread_entry, "Thread2", 512, 10, 5);
if (t1) rt_thread_startup(t1);
if (t2) rt_thread_startup(t2);
return 0;
}
关键:初始值为1,同一时刻只有一个线程能获取信号量,实现互斥访问。
场景三:事件触发 — 中断通知线程
static rt_sem_t event_sem = RT_NULL;
/* 模拟外部中断处理 */
void external_event_handler(void)
{
rt_sem_release(event_sem);
}
static void thread_entry(void *parameter)
{
while (1)
{
rt_sem_take(event_sem, RT_WAITING_FOREVER);
rt_kprintf("收到外部事件,处理中…\\n");
rt_thread_mdelay(1000);
}
}
二、互斥量(Mutex)
互斥量类似于洗手间的锁——任何时候只能有一个人使用,其他人必须排队等待。
2.1 互斥量 vs 信号量
| 所有权 | 只有获取者才能释放 | 任何线程都可以释放 |
| 优先级继承 | 支持(防止优先级反转) | 不支持 |
| 推荐场景 | 保护共享资源 | 任务同步/事件通知 |
2.2 核心API
| rt_mutex_create(name, flag) | 动态创建互斥量 |
| rt_mutex_take(mutex, time) | 获取互斥量 |
| rt_mutex_release(mutex) | 释放互斥量 |
| rt_mutex_delete(mutex) | 删除互斥量 |
2.3 应用示例
#include <rtthread.h>
rt_mutex_t mutex = RT_NULL;
void thread_entry1(void *parameter)
{
rt_kprintf("线程1: 请求使用共享资源\\n");
rt_mutex_take(mutex, RT_WAITING_FOREVER);
rt_kprintf("线程1: 正在使用共享资源\\n");
rt_thread_mdelay(1000);
rt_mutex_release(mutex);
rt_kprintf("线程1: 释放共享资源\\n");
}
void thread_entry2(void *parameter)
{
rt_kprintf("线程2: 请求使用共享资源\\n");
rt_mutex_take(mutex, RT_WAITING_FOREVER);
rt_kprintf("线程2: 正在使用共享资源\\n");
rt_thread_mdelay(1000);
rt_mutex_release(mutex);
rt_kprintf("线程2: 释放共享资源\\n");
}
int main(void)
{
mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);
rt_thread_t t1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 25, 10);
rt_thread_t t2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 25, 10);
if (t1) rt_thread_startup(t1);
if (t2) rt_thread_startup(t2);
return 0;
}
保护共享资源时,优先使用互斥量而非信号量,因为互斥量支持优先级继承,可以避免优先级反转问题。
三、事件集(Event)
事件集可以看作公交站等车:你可以等待多路公交,有一路来了就走(OR模式),或者等所有人到齐再走(AND模式)。
3.1 工作机制
事件集使用32位整数作为事件标志,每一位代表一个事件:
- 逻辑或(OR):任一事件发生即可唤醒线程
- 逻辑与(AND):所有指定事件都发生才唤醒线程
3.2 核心API
| rt_event_create(name, flag) | 创建事件集 |
| rt_event_send(event, set) | 发送事件(设置标志位) |
| rt_event_recv(event, set, option, timeout, recved) | 接收事件(等待标志位) |
| rt_event_delete(event) | 删除事件集 |
option 选项:
- RT_EVENT_FLAG_OR:任一事件满足即唤醒
- RT_EVENT_FLAG_AND:所有事件满足才唤醒
- RT_EVENT_FLAG_CLEAR:接收后清除事件标志
3.3 示例一:OR模式 — 等待任意公交到站
#include <rtthread.h>
rt_event_t event = RT_NULL;
void thread_entry1(void *parameter)
{
rt_uint32_t received;
rt_kprintf("乘客: 等待3路或5路公交…\\n");
rt_event_recv(event, (1 << 3 | 1 << 5),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &received);
rt_kprintf("乘客: 公交到站,出发!\\n");
}
void thread_entry2(void *parameter)
{
rt_thread_mdelay(500);
rt_kprintf("调度: 3路公交到站\\n");
rt_event_send(event, (1 << 3));
}
int main(void)
{
event = rt_event_create("event", RT_IPC_FLAG_PRIO);
rt_thread_t t1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 25, 10);
rt_thread_t t2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 25, 10);
if (t1) rt_thread_startup(t1);
if (t2) rt_thread_startup(t2);
return 0;
}
3.4 示例二:AND模式 — 等待所有条件满足
/* 运动员等待两个裁判同时发令 */
void athlete_entry(void *parameter)
{
rt_uint32_t received;
rt_kprintf("运动员: 等待准备信号和开始信号…\\n");
rt_event_recv(event, (1 << 1 | 1 << 2),
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &received);
rt_kprintf("运动员: 收到所有信号,起跑!\\n");
}
void referee1_entry(void *parameter)
{
rt_thread_mdelay(500);
rt_kprintf("裁判1: 发送准备信号\\n");
rt_event_send(event, (1 << 1));
}
void referee2_entry(void *parameter)
{
rt_thread_mdelay(1000);
rt_kprintf("裁判2: 发送开始信号\\n");
rt_event_send(event, (1 << 2));
}
第二部分:线程间通信
同步解决的是"时序协调"问题,通信解决的是"数据传递"问题。为什么不直接用全局变量?因为全局变量存在数据竞争、数据丢失、同步复杂等问题。RT-Thread提供了邮箱、消息队列和信号三种线程安全的通信机制。
四、邮箱(Mailbox)
邮箱类似于一个固定容量的信箱:每封"邮件"固定为 4字节(一个指针或整型值),效率高但容量有限。
4.1 核心API
| rt_mb_create(name, size, flag) | 动态创建邮箱(size为容量) |
| rt_mb_send(mb, value) | 发送邮件(4字节) |
| rt_mb_send_wait(mb, value, timeout) | 发送邮件(带超时) |
| rt_mb_recv(mb, value, timeout) | 接收邮件 |
| rt_mb_urgent_send(mb, value) | 发送紧急邮件(插入队首) |
| rt_mb_delete(mb) | 删除邮箱 |
4.2 示例一:简单消息传递
#include <rtthread.h>
rt_mailbox_t mb;
void thread_entry1(void *parameter)
{
char msg = 'A';
rt_kprintf("线程1:发送邮件 '%c'\\n", msg);
rt_mb_send(mb, (rt_uint32_t)msg);
}
void thread_entry2(void *parameter)
{
char msg;
rt_kprintf("线程2:等待邮件…\\n");
rt_mb_recv(mb, (rt_uint32_t *)&msg, RT_WAITING_FOREVER);
rt_kprintf("线程2:收到邮件 '%c'\\n", msg);
}
int main(void)
{
mb = rt_mb_create("mb", 4, RT_IPC_FLAG_PRIO);
rt_thread_t t1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 10, 10);
rt_thread_t t2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 10, 10);
if (t1) rt_thread_startup(t1);
if (t2) rt_thread_startup(t2);
return 0;
}
4.3 示例二:通过邮箱传递结构体指针
邮箱每封邮件只有4字节,传递复杂数据时可以发送结构体的地址:
struct sensor_data {
int temperature;
int humidity;
int acceleration;
};
static struct sensor_data global_data;
rt_mailbox_t mb;
void sender_entry(void *parameter)
{
global_data.temperature = 25;
global_data.humidity = 60;
global_data.acceleration = 10;
rt_mb_send(mb, (rt_uint32_t)&global_data); // 发送地址
}
void receiver_entry(void *parameter)
{
struct sensor_data *received;
rt_mb_recv(mb, (rt_uint32_t *)&received, RT_WAITING_FOREVER);
rt_kprintf("温度: %d°C, 湿度: %d%%, 加速度: %d m/s²\\n",
received->temperature, received->humidity, received->acceleration);
}
注意:通过邮箱传递指针时,必须确保指向的数据在接收方读取之前一直有效(使用全局变量或动态分配的内存)。
五、消息队列(Message Queue)
消息队列类似于快递柜:可以存放不同大小的包裹(消息),比邮箱更灵活,支持任意大小的消息。
5.1 核心API
| rt_mq_create(name, msg_size, max_msgs, flag) | 动态创建消息队列 |
| rt_mq_send(mq, buffer, size) | 发送消息 |
| rt_mq_send_wait(mq, buffer, size, timeout) | 发送消息(带超时) |
| rt_mq_urgent_send(mq, buffer, size) | 发送紧急消息 |
| rt_mq_recv(mq, buffer, size, timeout) | 接收消息 |
| rt_mq_delete(mq) | 删除消息队列 |
5.2 应用示例:传递传感器数据
#include <rtthread.h>
struct sensor_data {
int temperature;
int humidity;
int acceleration;
};
rt_mq_t mq;
void sender_entry(void *parameter)
{
struct sensor_data data = {25, 60, 10};
rt_kprintf("发送方:发送传感器数据\\n");
rt_mq_send(mq, &data, sizeof(data));
}
void receiver_entry(void *parameter)
{
struct sensor_data received;
rt_kprintf("接收方:等待传感器数据…\\n");
rt_mq_recv(mq, &received, sizeof(received), RT_WAITING_FOREVER);
rt_kprintf("温度: %d°C, 湿度: %d%%, 加速度: %d m/s²\\n",
received.temperature, received.humidity, received.acceleration);
}
int main(void)
{
mq = rt_mq_create("mq", sizeof(struct sensor_data), 4, RT_IPC_FLAG_PRIO);
rt_thread_t t1 = rt_thread_create("sender", sender_entry, RT_NULL, 1024, 10, 10);
rt_thread_t t2 = rt_thread_create("receiver", receiver_entry, RT_NULL, 1024, 10, 10);
if (t1) rt_thread_startup(t1);
if (t2) rt_thread_startup(t2);
return 0;
}
5.3 邮箱 vs 消息队列
| 消息大小 | 固定4字节 | 任意大小 |
| 效率 | 更高(固定大小,无拷贝开销) | 稍低(需要拷贝数据) |
| 灵活性 | 低(只能传递指针或整型) | 高(可传递任意结构体) |
| 适用场景 | 简单通知、传递指针 | 复杂数据传输 |
六、信号(Signal)
信号是一种异步通知机制,类似于紧急电话。发送方可以随时向目标线程发送信号,目标线程会中断当前工作,转而执行信号处理函数。
6.1 核心API
| rt_signal_install(signo, handler) | 安装信号处理函数 |
| rt_signal_unmask(signo) | 解除信号屏蔽(允许接收) |
| rt_thread_kill(tid, sig) | 向指定线程发送信号 |
| rt_signal_wait(set, si, timeout) | 等待信号 |
6.2 应用示例
#include <rtthread.h>
static rt_thread_t tid1 = RT_NULL;
void thread1_signal_handler(int sig)
{
rt_kprintf("线程1 收到信号 %d\\n", sig);
}
static void thread1_entry(void *parameter)
{
int cnt = 0;
/* 安装并启用信号 */
rt_signal_install(SIGUSR1, thread1_signal_handler);
rt_signal_unmask(SIGUSR1);
while (cnt < 10)
{
rt_kprintf("线程1 计数: %d\\n", cnt);
cnt++;
rt_thread_mdelay(100);
}
}
int main(void)
{
tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, 512, 25, 5);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
rt_thread_mdelay(300);
rt_thread_kill(tid1, SIGUSR1); // 300ms后发送信号
return 0;
}
同步与通信机制选择指南
| 信号量 | 控制资源访问数量 | 无 | 任务同步、资源计数 |
| 互斥量 | 保护共享资源(独占) | 无 | 串口/I2C互斥访问 |
| 事件集 | 等待多个事件组合 | 无 | 多条件触发 |
| 邮箱 | 传递4字节消息 | 有(4字节) | 简单通知、传递指针 |
| 消息队列 | 传递任意大小消息 | 有(任意) | 传感器数据、复杂消息 |
| 信号 | 异步紧急通知 | 无 | 异常处理、紧急事件 |
动态创建 vs 静态初始化
所有IPC对象(信号量、互斥量、事件集、邮箱、消息队列)都支持两种创建方式:
| 内存来源 | 堆(运行时分配) | 栈或静态区(编译时分配) |
| 灵活性 | 高(运行时创建/销毁) | 低(编译时确定) |
| 性能 | 有分配开销 | 无额外开销 |
| 安全性 | 可能内存泄漏 | 更安全 |
资源充足选动态创建,资源受限选静态初始化。
总结
本文介绍了RT-Thread的六种线程间同步与通信机制。核心原则:
掌握这些机制,你就掌握了多线程编程的核心。

