1. 基本概念
队列又称消息队列,是一种常用于线程间通信的数据结构,队列可以在线程与线程间、中断和线程间传送信息,实现了线程接收来自其他线程或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在线程自己的空间。线程能够从队列里面读取消息,当队列中的消息是空时,挂起读取线程,用户还可以指定挂起的线程时间 timeout;当队列中有新消息时,挂起的读取线程被唤醒并处理新消息,消息队列是一种异步的通信方式。
通过消息队列服务,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。同时 RT- Thread 中的消息队列支持优先级,也就是说在所有等待消息的线程中优先级最高的会先获得消息。
2. 运行机制
消息队列结构体:
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;
消息链表结构体:
struct rt_mq_message
{
struct rt_mq_message *next;
};
四个重要函数:
/**< 消息队列创建函数 */
rt_mq_t rt_mq_create(const char *name,
rt_size_t msg_size,
rt_size_t max_msgs,
rt_uint8_t flag);
/**< 消息队列发送函数 */
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size);
/**< 消息队列接收函数 */
rt_err_t rt_mq_recv(rt_mq_t mq,
void *buffer,
rt_size_t size,
rt_int32_t timeout);
/**< 消息队列删除函数 */
rt_err_t rt_mq_delete(rt_mq_t mq);
消息队列从初始状态到被写入一条消息,再被写入一条消息,然后被读出一条消息,再被读出一条消息,这一过程消息队列结构体的成员是如何变化的呢?消息池是如何被使用的呢?下图展示了这一过程各变量的变化情况:
3. 阻塞机制
我们使用的消息队列一般不是属于某个线程的队列,在很多时候,我们创建的队列,是每个线程都可以去对他进行读写操作的,但是为了保护每个线程对它进行读写操作的过程,我们必须要有阻塞机制,在某个线程对它读写操作的时候,必须保证该线程能正常完成读写操作,而不受后来的线程干扰,凡事都有先来后到嘛!
那么,如何实现这个先来后到的机制呢,很简单,因为 RT-Thread 已经为我们做好了,我们直接使用就好了,每个对消息队列读写的函数,都有这种机制,我称之为阻塞机制。假设有一个线程A 对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时线程 A有 3 个选择 :
- timeout=
RT_WAITING_NO
:宏RT_WAITING_NO
为0,这种情况线程 A 扭头就走,既然队列没有消息,那我也不等了,干其它事情去,这样子线程 A 不会进入阻塞态。 - timeout=
RT_WAITING_FOREVER
:这种情况线程 A 死等,不等到消息就不走了,这样子线程A 就会进入阻塞态,直到完成读取队列的消息。 - timeout=n, n>0:这种情况线程 A 还是在这里等n 个 tick ,在这 n 个 tick 到来之前线程 A 都是处于阻塞态,当阻塞的这段时间线程 A 等到了队列的消息,那么线程 A 就会从阻塞态变成就绪态,如果此时线程 A比当前运行的线程优先级还高,那么,线程 A 就会得到消息并且运行;假如 n个 tick 都过去了,队列还没消息,那线程 A 就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行线程 A 的其他代码。