线程邮箱框架与示例
一、为什么需要线程邮箱?
在硬件驱动的应用层代码编写时,要想完成多个任务的同时操作,一般需要多线程,比如需要信息采集、屏幕显示、网络发送、信息存储都要同时进行,每采集一次数据,都要发送给屏幕进行显示,还要通过网络进行发送,以及存储数据。
在之前的学习中,可以用一个全局变量来完成, 因为所有的线程都在同一个进程中,把采集到的信息放到全局变量中,其他线程去拿数据即可,
但是这种方法有个弊端,就是当其他线程拿取数据的速率不匹配的话,有的快有的慢,会影响系统效率,那就不能用简单的全局变量来完成。
比如有的传感器采集的速度很快,而显示模块,一般一秒显示一次,那就会出现数据丢失的情况,所以为了让多个任务的并行时,数据不丢失,就采用一种队列的形式。
就是不搞一个全局变量,而是搞很多全局变量,构建成一个队列。
全局变量队列:
数据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){// 内存分配失败,返回NULLreturn 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){// 内存分配失败,返回-1return -1;} // 创建新线程,执行指定的线程入口函数ret = pthread_create(&pnewthread->tid, NULL, pfun, NULL);if (ret != 0){// 线程创建失败,释放已分配的内存并返回-1free(pnewthread);return -1;}// 为线程的消息队列头分配内存pnewthread->ptaskmsghead = malloc(sizeof(struct list_head));if (NULL == pnewthread->ptaskmsghead){// 内存分配失败,返回-1return -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){// 未找到目的线程节点,返回-1return -1;}// 2. 为存放数据的消息任务结构体分配内存pmsg = malloc(sizeof(taskmsg_t));if (NULL == pmsg){// 内存分配失败,返回-1return -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){// 未找到当前线程节点,返回-1return -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;
}