线程邮箱框架与示例

一、为什么需要线程邮箱?

在硬件驱动的应用层代码编写时,要想完成多个任务的同时操作,一般需要多线程,比如需要信息采集、屏幕显示、网络发送、信息存储都要同时进行,每采集一次数据,都要发送给屏幕进行显示,还要通过网络进行发送,以及存储数据。

在之前的学习中,可以用一个全局变量来完成, 因为所有的线程都在同一个进程中,把采集到的信息放到全局变量中,其他线程去拿数据即可,

但是这种方法有个弊端,就是当其他线程拿取数据的速率不匹配的话,有的快有的慢,会影响系统效率,那就不能用简单的全局变量来完成。

比如有的传感器采集的速度很快,而显示模块,一般一秒显示一次,那就会出现数据丢失的情况,所以为了让多个任务的并行时,数据不丢失,就采用一种队列的形式。

就是不搞一个全局变量,而是搞很多全局变量,构建成一个队列。

全局变量队列:

|-----|-----|-----|-----|-----|-----|
| 数据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;
}
相关推荐
To_OC1 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495642 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
Yana.nice2 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
DFT计算杂谈6 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习