RT-Thread学习笔记(8):消息队列

基本概念

消息队列,是一种常用于线程间通信的数据结构,队列可以在线程与线程间中断和线程间传送信息,实现了线程接收来自其他线程或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在线程自己的空间。线程能够从队列里面读取消息,当队列中的消息是空时,挂起读取线程,用户还可以指定挂起的线程时间timeout;当队列中有新消息时,挂起的读取线程被唤醒并处理新消息,消息队列是一种异步的通信方式。

通过消息队列服务,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。同时RT-Thread中的消息队列支持优先级,也就是说在所有等待消息的线程中优先级最高的会先获得消息。

用户在处理业务时,消息队列提供了异步处理机制,允许将一个消息放入队列,但并不立即处理它,同时队列还能起到缓冲消息作用。

RT-Thread中使用队列数据结构实现线程异步通信工作,具有如下特性:

  • 消息支持先进先出方式排队与优先级排队方式,支持异步读写工作方式。
  • 读队列支持超时机制。
  • 支持发送紧急消息,这里的紧急消息是往队列头发送消息。
  • 可以允许不同长度(不超过队列节点最大值)的任意类型消息。
  • 一个线程能够从任意一个消息队列接收和发送消息。
  • 多个线程能够从同一个消息队列接收和发送消息。
  • 当队列使用结束后,需要通过删除队列操作释放内存函数回收。

消息队列的运作机制

创建消息队列时先创建一个消息队列对象控制块,然后给消息队列分配一块内存空间,组织成空闲消息链表,这块内存的大小等于[消息大小+消息头(用于链表连接)]与消息队列容量的乘积,接着再初始化消息队列,此时消息队列为空。

struct rt_messagequeue
{
    struct rt_ipc_object parent;

    void* msg_pool;                     /* 指向存放消息的缓冲区的指针 */

    rt_uint16_t msg_size;               /* 每个消息的长度 */
    rt_uint16_t max_msgs;               /* 最大能够容纳的消息数 */

    rt_uint16_t entry;                  /* 队列中已有的消息数 */

    void* msg_queue_head;               /* 消息链表头 */
    void* msg_queue_tail;               /* 消息链表尾 */
    void* msg_queue_free;               /* 空闲消息链表 */

    rt_list_t suspend_sender_thread;    /* 发送线程的挂起等待队列 */
};
typedef struct rt_messagequeue* rt_mq_t;

在这里插入图片描述
线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。

发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。

读取消息时,根据msg_queue_head找到最先入队列中的消息节点进行读取。根据消息队列控制块中的entry判断队列是否有消息读取,对全部空闲(entry为0)队列进行读消息操作会引起线程挂起。

消息队列的阻塞机制

我们使用的消息队列一般不是属于某个线程的队列,在很多时候,我们创建的队列,是每个线程都可以去对他进行读写操作的,但是为了保护每个线程对它进行读写操作的过程,我们必须要有阻塞机制,在某个线程对它读写操作的时候,必须保证该线程能正常完成读写操作,而不受后来的线程干扰,凡事都有先来后到嘛!

那么,如何实现这个先来后到的机制呢,很简单,因为RT-Thread已经为我们做好了,我们直接使用就好了,每个对消息队列读写的函数,都有这种机制,我称之为阻塞机制。假设有一个线程A对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时线程A有3个选择:第一个选择,线程A扭头就走,既然队列没有消息,那我也不等了,干其它事情去,这样子线程A不会进入阻塞态;第二个选择,线程A还是在这里等等吧,可能过一会队列就有消息,此时线程A会进入阻塞状态,在等待着消息的道来,而线程A的等待时间就由我们自己定义,比如设置1000个tick的等待,在这1000个tick到来之前线程A都是处于阻塞态,当阻塞的这段时间线程A等到了队列的消息,那么线程A就会从阻塞态变成就绪态,如果此时线程A比当前运行的线程优先级还高,那么,线程A就会得到消息并且运行;假如1000个tick都过去了,队列还没消息,那线程A就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行线程A的其他代码;第三个选择,线程A死等,不等到消息就不走了,这样子线程A就会进入阻塞态,直到完成读取队列的消息。

而在发送消息操作的时候,为了保护数据,当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL),发送消息并不带有阻塞机制的,因为发送消息的环境可能是在中断中,不允许有阻塞的情况。

没有消息,不等,直接走
只给你1000个tick的时间
时间到读到数据
时间到没读到数据
死等
读到数据
没读到数据
A
就绪/运行
阻塞
就绪/运行
阻塞
就绪/运行
阻塞

消息队列的应用场景

消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及在中断服务函数中给线程发送消息(中断服务例程不可能接收消息)。

消息队列图示

创建新队列

在这里插入图片描述

A线程写入一个变量

在这里插入图片描述

A线程修改变量

在这里插入图片描述

B线程读取队列

在这里插入图片描述

消息队列控制块

在这里插入图片描述

发送消息
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
{
    return rt_mq_send_wait(mq, buffer, size, 0);
}
rt_mq_send_wait
	/* 如果不成功:当前线程挂起, 并放入链表: mq->suspend_sender_thread */
	rt_ipc_list_suspend(&(mq->suspend_sender_thread),thread,mq->parent.parent.flag);
	/* 如果指定了超时时间: 最多等多久
	* 设置、启动线程定时器
	*/
	rt_timer_start(&(thread->thread_timer));

一个线程写队列时,如果队列已经满了,它会被挂起,何时被唤醒?

  • 超时

在这里插入图片描述

  • 别的线程读取队列,此时就会空出来一个空闲消息

在这里插入图片描述

参考

本节笔记参考于:野火-《RT-Thread内核实现与应用开发实战》
韦东山 《RT-Thread完全开发手册之内核机制》
以及RT-Thread官网:消息队列

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值