一、为什么需要线程邮箱?
在硬件驱动的应用层代码编写时,要想完成多个任务的同时操作,一般需要多线程,比如需要信息采集、屏幕显示、网络发送、信息存储都要同时进行,每采集一次数据,都要发送给屏幕进行显示,还要通过网络进行发送,以及存储数据。
在之前的学习中,可以用一个全局变量来完成, 因为所有的线程都在同一个进程中,把采集到的信息放到全局变量中,其他线程去拿数据即可,
但是这种方法有个弊端,就是当其他线程拿取数据的速率不匹配的话,有的快有的慢,会影响系统效率,那就不能用简单的全局变量来完成。
比如有的传感器采集的速度很快,而显示模块,一般一秒显示一次,那就会出现数据丢失的情况,所以为了让多个任务的并行时,数据不丢失,就采用一种队列的形式。
就是不搞一个全局变量,而是搞很多全局变量,构建成一个队列。
全局变量队列:
|-----|-----|-----|-----|-----|-----|
| 数据1 | 数据2 | 数据3 | 数据4 | 数据5 | 数据6 |
每个数据不管是采集还是显示,都排队等待,需要的时候就出队。
但是,只搞一个队列的话,只能让两个线程去保证数据不丢失,还得有网络发送以及数据存储,因此,就需要 给每一个线程都分配一个队列。这种数据结构就称为线程邮箱。
二、什么是线程邮箱?
指的是多线程之间通信的一种数据结构。每个线程都有一个队列,通过队列来实现数据缓存和通信。
三、线程邮箱有几种实现方式?
1.通过系统 5 中的消息队列实现通信(较为简单)
进程间通信方式:消息队列、共享内存、信号量。
消息队列不仅进程间可以用,线程间也可以。
具体思路:
ftok(产生键值)、msgget(创建消息队列)、msgsnd(发送)、msgrcv(接收)
创建几个线程就创建几个消息队列。
2.手写链表结构封装来实现
我们选择第二种来实现,既可以锻炼我们的写代码能力,并且链表的移植性也比较好,只要支持C语言的系统都可以实现。
选择内核链表来实现,加上一个 list.h 的头文件。
线程邮箱具体实现:
mailbox.h :
#ifndef __MAILBOX_H__
#define __MAILBOX_H__
#include "list.h"
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
// 线程任务结构体,用于表示一个线程及其相关信息
typedef struct taskthread
{
pthread_t tid; // 线程ID,用于唯一标识一个线程
char name[32]; // 线程名,方便对线程进行识别和管理
void *(*pfun)(void *); // 线程入口函数,线程启动后将执行该函数
struct list_head *ptaskmsghead; // 消息队列头,该线程对应的消息队列的头部指针
struct list_head node; // 链表节点,用于将该线程任务结构体插入到线程任务链表中
}taskthread_t;
// 消息任务结构体,用于表示要传递的消息
typedef struct taskmsg
{
char sender[32]; // 发送者,记录消息的发送者名称
double hum; // 湿度,消息中携带的湿度数据
double temp; // 温度,消息中携带的温度数据
double distence; // 距离,消息中携带的距离数据
struct list_head node; // 链表节点,用于将该消息任务结构体插入到消息队列中
}taskmsg_t;
// 函数声明:创建线程邮箱头
extern struct list_head *mailbox_create(void);
// 函数声明:将新的线程任务加入队列中
extern int mailbox_addtask(struct list_head *phead, char *pthreadname, void *(*pfun)(void*));
// 函数声明:向目的线程发送数据
extern int mailbox_sendmsg(struct list_head *ptaskthreadhead, char *ptaskname, double hum, double temp, double dis);
// 函数声明:接收自己线程的数据
extern int mailbox_recvmsg(struct list_head *ptaskthreadhead, double *phum, double *ptemp, double *pdis);
// 函数声明:销毁线程邮箱
extern void mailbox_destroy(struct list_head *phead);
#endif
mailbox.c:
#include "mailbox.h"
// 创建线程邮箱头,返回一个指向链表头的指针
struct list_head *mailbox_create(void)
{
struct list_head *phead = NULL;
// 为链表头分配内存
phead = malloc(sizeof(struct list_head));
if (NULL == phead)
{
// 内存分配失败,返回NULL
return NULL;
}
// 初始化链表头
INIT_LIST_HEAD(phead);
return phead;
}
// 将新的线程任务加入队列中,成功返回0,失败返回-1
int mailbox_addtask(struct list_head *phead, char *pthreadname, void *(*pfun)(void*))
{
taskthread_t *pnewthread = NULL;
int ret = 0;
// 为新的线程任务结构体分配内存
pnewthread = malloc(sizeof(taskthread_t));
if (NULL == pnewthread)
{
// 内存分配失败,返回-1
return -1;
}
// 创建新线程,执行指定的线程入口函数
ret = pthread_create(&pnewthread->tid, NULL, pfun, NULL);
if (ret != 0)
{
// 线程创建失败,释放已分配的内存并返回-1
free(pnewthread);
return -1;
}
// 为线程的消息队列头分配内存
pnewthread->ptaskmsghead = malloc(sizeof(struct list_head));
if (NULL == pnewthread->ptaskmsghead)
{
// 内存分配失败,返回-1
return -1;
}
// 初始化线程的消息队列头
INIT_LIST_HEAD(pnewthread->ptaskmsghead);
// 复制线程名到线程任务结构体中
strncpy(pnewthread->name, pthreadname, sizeof(pnewthread->name)-1);
// 设置线程入口函数
pnewthread->pfun = pfun;
// 将新的线程任务结构体插入到线程任务链表的尾部
list_add_tail(&pnewthread->node, phead);
return 0;
}
// 向目的线程发送数据,成功返回0,失败返回-1
int mailbox_sendmsg(struct list_head *ptaskthreadhead, char *ptaskname, double hum, double temp, double dis)
{
taskthread_t *ptmptask = NULL;
int is_exist = 0;
taskmsg_t *pmsg = NULL;
// 1. 遍历线程任务链表,找到目的线程节点
list_for_each_entry(ptmptask, ptaskthreadhead, node)
{
if (0 == strcmp(ptmptask->name, ptaskname))
{
// 找到目的线程节点,标记为存在
is_exist = 1;
break;
}
}
if (!is_exist)
{
// 未找到目的线程节点,返回-1
return -1;
}
// 2. 为存放数据的消息任务结构体分配内存
pmsg = malloc(sizeof(taskmsg_t));
if (NULL == pmsg)
{
// 内存分配失败,返回-1
return -1;
}
// 初始化消息任务结构体的链表节点
INIT_LIST_HEAD(&pmsg->node);
// 设置消息中的湿度数据
pmsg->hum = hum;
// 设置消息中的温度数据
pmsg->temp = temp;
// 设置消息中的距离数据
pmsg->distence = dis;
// 3. 将消息任务结构体插入到目的线程的消息队列尾部
list_add_tail(&pmsg->node, ptmptask->ptaskmsghead);
return 0;
}
// 接收自己线程的数据,成功返回0,失败返回-1
int mailbox_recvmsg(struct list_head *ptaskthreadhead, double *phum, double *ptemp, double *pdis)
{
taskthread_t *ptmptask = NULL;
int is_exist = 0;
taskmsg_t *pmsg = NULL;
// 1. 遍历线程任务链表,找到当前线程节点
list_for_each_entry(ptmptask, ptaskthreadhead, node)
{
if (ptmptask->tid == pthread_self())
{
// 找到当前线程节点,标记为存在
is_exist = 1;
break;
}
}
if (!is_exist)
{
// 未找到当前线程节点,返回-1
return -1;
}
// 只要队列为空,则忙等待
while (list_empty(ptmptask->ptaskmsghead));
// 2. 从消息队列尾部取出消息
pmsg = list_entry(ptmptask->ptaskmsghead->prev, taskmsg_t, node);
// 从消息队列中删除该消息
list_del(&pmsg->node);
// 将消息中的湿度数据赋值给传入的指针
*phum = pmsg->hum;
// 将消息中的温度数据赋值给传入的指针
*ptemp = pmsg->temp;
// 将消息中的距离数据赋值给传入的指针
*pdis = pmsg->distence;
// 释放消息任务结构体占用的内存
free(pmsg);
return 0;
}
// 销毁线程邮箱
void mailbox_destroy(struct list_head *phead)
{
taskthread_t *ptmptask1 = NULL;
taskthread_t *ptmptask2 = NULL;
taskmsg_t *pmsg1 = NULL;
taskmsg_t *pmsg2 = NULL;
// 1. 遍历线程任务链表,安全地删除每个线程任务及其消息队列中的消息
list_for_each_entry_safe(ptmptask1, ptmptask2, phead, node)
{
// 遍历线程的消息队列,安全地删除每个消息
list_for_each_entry_safe(pmsg1, pmsg2, ptmptask1->ptaskmsghead, node)
{
// 从消息队列中删除该消息
list_del(&pmsg1->node);
// 释放消息任务结构体占用的内存
free(pmsg1);
}
// 从线程任务链表中删除该线程任务
list_del(&ptmptask1->node);
// 释放线程任务结构体占用的内存
free(ptmptask1);
}
return;
}
main.c:
#include "mailbox.h"
#include <stdio.h>
// 邮箱全局地址,用于存储线程任务链表的头指针
struct list_head *ptasklist = NULL;
// 采集线程函数,用于生成随机的温度、湿度和距离数据并发送给显示线程
void *collect_thread(void *arg)
{
double temp = 0;
double hum = 0;
double dis = 0;
// 初始化随机数种子
srand(time(NULL));
while (1)
{
// 生成0-100之间的随机温度数据
temp = rand() % 10000 / 100.0;
// 生成0-100之间的随机湿度数据
hum = rand() % 10000 / 100.0;
// 生成0-100之间的随机距离数据
dis = rand() % 10000 / 100.0;
// 向显示线程发送数据
mailbox_sendmsg(ptasklist, "显示线程", temp, hum, dis);
// 打印发送的数据
printf("send:temp = %.2lf, hum = %.2lf, dis = %.2lf\n", temp, hum, dis);
// 线程休眠2秒
sleep(2);
}
return NULL;
}
// 显示线程函数,用于接收采集线程发送的数据并打印
void *display_thread(void *arg)
{
double temp = 0;
double hum = 0;
double dis = 0;
while (1)
{
// 接收数据
mailbox_recvmsg(ptasklist, &temp, &hum, &dis);
// 打印接收到的数据
printf("recv:temp = %.2lf, hum = %.2lf, dis = %.2lf\n", temp, hum, dis);
}
return NULL;
}
// 网络线程函数,用于接收采集线程发送的数据并打印
void *network_thread(void *arg)
{
double temp = 0;
double hum = 0;
double dis = 0;
while (1)
{
// 接收数据
mailbox_recvmsg(ptasklist, &temp, &hum, &dis);
// 打印接收到的数据
printf("temp = %.2lf, hum = %.2lf, dis = %.2lf\n", temp, hum, dis);
}
return NULL;
}
int main(void)
{
// 创建线程邮箱头
ptasklist = mailbox_create();
// 添加采集线程任务
mailbox_addtask(ptasklist, "采集线程", collect_thread);
// 添加显示线程任务
mailbox_addtask(ptasklist, "显示线程", display_thread);
// 添加网络线程任务
mailbox_addtask(ptasklist, "网络线程", network_thread);
while (1)
{
// 主循环,保持程序运行
}
// 销毁线程邮箱
mailbox_destroy(ptasklist);
return 0;
}