线程邮箱 (thmailbox)
基于定长块内存池的零拷贝线程间消息邮箱。消息在环形队列中只传递指针而不拷贝数据,配合缓冲区所有权转移机制,适用于音频、显示等大块数据的流水线式生产者-消费者场景。
数据结构
struct thmailbox_t {
unsigned char * pool; /* 定长块内存池 */
unsigned int block_size; /* 每块负载容量 */
unsigned int block_count; /* 块数量 */
void * freelist; /* 空闲块链表 */
unsigned int freecount; /* 空闲块计数 */
void ** ring; /* 消息描述符环形队列 */
unsigned int ring_size; /* 队列长度(2 的幂次方) */
unsigned int in; /* 写入位置 */
unsigned int out; /* 读取位置 */
struct mutex_t lock; /* 互斥锁,保护池和队列操作 */
struct semaphore_t ssem; /* 发送信号量,队列有空位时通知发送方 */
struct semaphore_t rsem; /* 接收信号量,队列有消息时通知接收方 */
struct semaphore_t psem; /* 池信号量,有块归还时通知等待分配的线程 */
};
工作原理
定长块内存池
邮箱在创建时一次性分配 count 个 [块头][负载] 布局的定长块:
- 块头隐藏:每块负载前有一个内部
bufhdr_t头(魔数、消息长度、所属邮箱指针),调用方拿到的永远是纯负载指针 - 空闲链表:空闲块通过块头的
next字段串成单向链表,不占用额外内存;块归还时魔数被next覆盖,天然识别非法的双重释放 - 提前校验:
thmailbox_buf_alloc()在写入数据前就检查len <= block_size,消息过长立即返回NULL
零拷贝与所有权转移
thchannel 逐字节拷贝数据,而邮箱只搬运指针:
- 发送方
thmailbox_buf_alloc()从池中取块(所有权归属发送方) - 直接写入块内,
thmailbox_send()将{指针, 长度}描述符入队,所有权移交给接收方 - 接收方
thmailbox_recv()取出指针直接读取,用完后thmailbox_buf_free()归还池
发送后发送方不得再访问该块;接收方用完必须归还,否则池逐渐耗尽。
线程同步
- 互斥锁(
lock):保护空闲链表、in/out指针和描述符队列的原子访问 - 发送信号量(
ssem):队列满时发送方等待;接收方取走消息后唤醒 - 接收信号量(
rsem):队列空时接收方等待;发送方投递消息后唤醒 - 池信号量(
psem):池耗尽时thmailbox_buf_alloc()阻塞等待;块被归还时唤醒——这为生产方提供了天然背压
超时机制
thmailbox_buf_alloc()、thmailbox_send() 和 thmailbox_recv() 均支持超时参数:
timeout < 0:永久阻塞timeout = 0:非阻塞模式,立即返回timeout > 0:阻塞等待指定毫秒数
send/recv 返回消息长度(0 表示失败或超时),因此消息长度不允许为 0。
API
| 函数 | 说明 |
|---|---|
thmailbox_alloc(size, count) | 分配邮箱,size 为单块负载容量,count 为块数量(同时决定队列深度,自动取整为 2 的幂次方) |
thmailbox_free(m) | 释放邮箱及其内存池、队列、互斥锁和信号量 |
thmailbox_reset(m) | 重置邮箱,将队列中的消息按所有权规则归还池并清空 |
thmailbox_buf_alloc(m, len, timeout) | 从池中分配一块缓冲区,支持超时,池耗尽返回 NULL |
thmailbox_buf_free(m, buf) | 将缓冲区归还池(接收方消费完毕后调用) |
thmailbox_buf_available(m) | 获取池中空闲块数量 |
thmailbox_send(m, buf, len, timeout) | 阻塞式投递消息(移交 buf 所有权),返回消息长度 |
thmailbox_recv(m, &pbuf, timeout) | 阻塞式接收消息,数据指针写入 pbuf,返回消息长度 |