欢迎光临
我们一直在努力

(四)RT-Thread线程间同步与通信

RT-Thread线程间同步与通信 — 多线程协作的核心

前言

在多线程系统中,线程之间需要"协调工作"和"交换信息"。RT-Thread提供了信号量、互斥量、事件集三种同步机制,以及邮箱、消息队列、信号三种通信机制。本文将逐一介绍它们的原理、API和实战用法。


第一部分:线程间同步

多个线程同时访问共享资源可能导致数据错乱,同步机制保证线程有序协作。


一、信号量(Semaphore)

信号量可以理解为电影院的座位数。当座位满了,新观众(线程)必须等待前面的人离开(释放资源)。

1.1 工作机制
  • 获取信号量(P操作):信号量值 > 0 时获取成功,值减1;值为0时线程等待。
  • 释放信号量(V操作):信号量值加1,唤醒等待的线程。
1.2 核心API
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
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
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
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
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
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对象(信号量、互斥量、事件集、邮箱、消息队列)都支持两种创建方式:

特性动态创建(create/delete)静态初始化(init/detach)
内存来源 堆(运行时分配) 栈或静态区(编译时分配)
灵活性 高(运行时创建/销毁) 低(编译时确定)
性能 有分配开销 无额外开销
安全性 可能内存泄漏 更安全

资源充足选动态创建,资源受限选静态初始化。


总结

本文介绍了RT-Thread的六种线程间同步与通信机制。核心原则:

  • 保护共享资源用互斥量
  • 任务间时序协调用信号量或事件集
  • 传递数据用邮箱(简单)或消息队列(复杂)
  • 异步紧急通知用信号
  • 掌握这些机制,你就掌握了多线程编程的核心。

    赞(0)
    未经允许不得转载:171主机测评 » (四)RT-Thread线程间同步与通信
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址