邮箱#

数据竞争与同步问题#

多线程环境下,如果多个线程同时读写⼀个全局变量,⽽没有使⽤锁或同步机制,会导致数据竞争,即多个线程同时操作全局变量,导致结果不确定或数据错误

邮箱/消息队列的优势:#

邮箱和消息队列都是线程安全的通信机制,能够保证消息传递的原⼦性和数据⼀致性。在使⽤这些⼯具时,操作系统会⾃动处理线程间的同步问题,不会发⽣类似的数据竞争。

如果 thread1 先将 global_value 设置为 10,然后 thread2 将其改为 20,⽽ thr ead3 需要读取这个值,那么 thread3

可能读取到的是最新的 20,⽽忽略了中间的10。也就是说,如果通信信息被新值覆盖了,旧值将永远丢失。

邮箱/消息队列的优势:#

邮箱和消息队列可以缓冲多个消息,并且消息不会被覆盖。当多个线程发送消息时,这些消息会被保存在队列中,直到⽬标线程逐⼀处理所有消息。这确保了数据的完整性和顺序性

邮箱#

邮箱在操作系统中类似于⼀个专⻔⽤来传递“邮件”的信箱。线程 1 可以将⼀封邮件(即 4 字节的消息)放⼊邮箱,线程 2 则可以从邮箱中接收到这封邮件。这种机制适合多个线程相互之间传递简单的数据或信息。

邮箱的⼯作机制#

发送邮件:线程将⼀条 4 字节的数据(邮件)发送到邮箱。如果邮箱满了,发送⽅可以选择等待,直到邮箱中有空位。 接收邮件:接收⽅线程从邮箱中读取邮件。如果邮箱为空,接收⽅可以选择等待,直到邮箱中有新邮件。

  • 线程1 发送⼀封邮件(字符 ‘A’)

  • 线程2 从邮箱中接收邮件,并打印出来

/*
 * Copyright (c) 2006-2025, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2025-02-06     RT-Thread    first version
 */

#include <rtthread.h> // 包含RT-Thread的核心头文件

#define DBG_TAG "main"    // 定义日志标签
#define DBG_LVL DBG_LOG   // 定义日志级别为普通日志
#include <rtdbg.h>        // 包含调试日志的头文件

// 定义邮件框句柄
rt_mailbox_t mb;

// 线程1的入口函数,用于发送邮件
void thread_entry1(void *parameter)
{
    char aaa_send = 'A'; // 定义要发送的邮件内容,这里是一个字符 'A'

    // 打印日志:线程1发送邮件
    rt_kprintf("线程1发送邮件...\n");

    // 使用 rt_mb_send 将邮件发送到邮件框
    // 参数说明:
    // mb: 邮件框句柄
    // (rt_uint32_t)aaa_send: 要发送的邮件内容(强制转换为 rt_uint32_t 类型)
    rt_mb_send(mb, (rt_uint32_t)aaa_send);
}

// 线程2的入口函数,用于接收邮件
void thread_entry2(void *parameter)
{
    char aaa_recv; // 定义用于接收邮件内容的变量

    // 打印日志:线程2等待接收邮件
    rt_kprintf("线程2等待接收...\n");

    // 使用 rt_mb_recv 从邮件框接收邮件
    // 参数说明:
    // mb: 邮件框句柄
    // (rt_ubase_t*)&aaa_recv: 邮件内容的接收地址(强制转换为 rt_ubase_t 类型)
    // RT_WAITING_FOREVER: 等待时间,表示无限等待
    rt_mb_recv(mb, (rt_ubase_t*)&aaa_recv, RT_WAITING_FOREVER);

    // 打印接收到的邮件内容
    rt_kprintf("线程2接收邮件:%c\n", aaa_recv);
}

// 主函数
int main(void)
{
    // 创建邮件框
    // 参数说明:
    // "mb": 邮件框名称
    // 4: 邮件框的容量(表示可以存储的消息数量)
    // RT_IPC_FLAG_PRIO: 设置邮件框的模式为优先级继承,防止优先级倒置
    mb = rt_mb_create("mb", 4, RT_IPC_FLAG_PRIO);

    // 创建线程1
    // 参数说明:
    // "thread1": 线程名称
    // thread_entry1: 线程入口函数
    // RT_NULL: 线程参数
    // 1024: 线程栈大小(单位:字节)
    // 25: 线程优先级(数值越小,优先级越高)
    // 10: 线程时间片
    rt_thread_t thread1 = rt_thread_create("thread1", thread_entry1, RT_NULL, 1024, 25, 10);

    // 创建线程2
    // 参数说明:
    // "thread2": 线程名称
    // thread_entry2: 线程入口函数
    // RT_NULL: 线程参数
    // 1024: 线程栈大小(单位:字节)
    // 25: 线程优先级(数值越小,优先级越高)
    // 10: 线程时间片
    rt_thread_t thread2 = rt_thread_create("thread2", thread_entry2, RT_NULL, 1024, 25, 10);

    // 如果线程1创建成功,则启动线程1
    if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }

    // 如果线程2创建成功,则启动线程2
    if (thread2 != RT_NULL)
    {
        rt_thread_startup(thread2);
    }

    // 主函数返回0,表示正常结束
    return 0;
}

实现效果: image-20250207154306309

遇到的问题:

rt_mb_recv(mb,(rt_ubase_t*)&aaa_recv,RT_WAITING_FOREVER);
//需要用(rt_ubase_t*),否则报错

消息队列#

你和朋友在共享⼀个快递柜(消息队列),你们可以向柜⼦⾥放⼊不同⼤⼩的包裹(消息),快递柜负责保管这些包裹。另⼀个朋友则可以从快递柜中取出包裹并处理,⽆论是⼤包裹还是⼩包裹都可以轻松处理。

消息队列的⼯作机制#

发送消息:线程可以将任意⼤⼩的消息发送到消息队列。如果消息队列满了,线程会等待,直到有空间可以存储新消息。

接收消息:线程可以从消息队列中接收消息。如果消息队列为空,线程可以选择等待新的消息到来。

  • thread_entry1 发送⼀条消息到消息队列

  • thread_entry2 从消息队列接收并打印消息

/*
 * Copyright (c) 2006-2025, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2025-02-06     RT-Thread    first version
 */

#include <rtthread.h> // 包含RT-Thread的核心头文件

#define DBG_TAG "main"    // 定义日志标签
#define DBG_LVL DBG_LOG   // 定义日志级别为普通日志
#include <rtdbg.h>        // 包含调试日志的头文件

// 定义消息队列句柄
rt_mq_t mq;

// 线程1的入口函数,用于发送消息
void thread_entry1(void *parameter)
{
    char message[] = "Hello,RT-Thread!"; // 定义要发送的消息内容

    // 打印日志:线程1发送邮件
    rt_kprintf("线程1发送邮件...\n");

    // 使用 rt_mq_send 将消息发送到消息队列
    // 参数说明:
    // mq: 消息队列句柄
    // message: 要发送的消息内容
    // sizeof(message): 消息的大小(字节)
    rt_mq_send(mq, message, sizeof(message));
}

// 线程2的入口函数,用于接收消息
void thread_entry2(void *parameter)
{
    char buffer[32]; // 定义用于接收消息内容的缓冲区

    // 打印日志:线程2等待接收消息
    rt_kprintf("线程2等待接收...\n");

    // 使用 rt_mq_recv 从消息队列接收消息
    // 参数说明:
    // mq: 消息队列句柄
    // buffer: 消息内容的接收缓冲区
    // sizeof(buffer): 缓冲区的大小(字节)
    // RT_WAITING_FOREVER: 等待时间,表示无限等待
    rt_mq_recv(mq, buffer, sizeof(buffer), RT_WAITING_FOREVER);

    // 打印接收到的消息内容
    rt_kprintf("线程2收到消息队列消息:%s\n", buffer);
}

// 主函数
int main(void)
{
    // 创建消息队列
    // 参数说明:
    // "mq": 消息队列名称
    // 32: 每条消息的大小(字节)
    // 4: 消息队列的容量(表示可以存储的消息数量)
    // RT_IPC_FLAG_PRIO: 设置消息队列的模式为优先级继承,防止优先级倒置
    mq = rt_mq_create("mq", 32, 4, RT_IPC_FLAG_PRIO);

    // 创建线程1
    // 参数说明:
    // "thread1": 线程名称
    // thread_entry1: 线程入口函数
    // RT_NULL: 线程参数
    // 1024: 线程栈大小(单位:字节)
    // 25: 线程优先级(数值越小,优先级越高)
    // 10: 线程时间片
    rt_thread_t thread1 = rt_thread_create("thread1", thread_entry1, RT_NULL, 1024, 25, 10);

    // 创建线程2
    // 参数说明:
    // "thread2": 线程名称
    // thread_entry2: 线程入口函数
    // RT_NULL: 线程参数
    // 1024: 线程栈大小(单位:字节)
    // 25: 线程优先级(数值越小,优先级越高)
    // 10: 线程时间片
    rt_thread_t thread2 = rt_thread_create("thread2", thread_entry2, RT_NULL, 1024, 25, 10);

    // 如果线程1创建成功,则启动线程1
    if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }

    // 如果线程2创建成功,则启动线程2
    if (thread2 != RT_NULL)
    {
        rt_thread_startup(thread2);
    }

    // 主函数返回0,表示正常结束
    return 0;
}

实现效果: image-20250207160620462