线程邮箱框架与示例

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

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

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

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

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

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

全局变量队列:

|-----|-----|-----|-----|-----|-----|
| 数据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;
}
相关推荐
Auc2412 分钟前
Java 原生实现代码沙箱(OJ判题系统第1期)——设计思路、实现步骤、代码实现
java·开发语言·python
THe CHallEnge of THe BrAve17 分钟前
Linux-openeuler更换yum镜像源
linux·运维·服务器
赵和范19 分钟前
C++:求分数序列和
开发语言·c++·算法
oioihoii21 分钟前
C++23 中的 views::chunk:深入探索与应用
开发语言·python·c++23
cs82198483125 分钟前
QT 解决msvc fatal error C1060: 编译器的堆空间不足
开发语言·qt
熊猫的反手凶变直线28 分钟前
Java-Lambda 表达式
java·开发语言·windows·笔记
在成都搬砖的鸭鸭28 分钟前
【Go底层】http标准库服务端实现原理
开发语言·http·golang
Super_man5418830 分钟前
k8s之service解释以及定义
java·开发语言·云原生·容器·kubernetes
fie888933 分钟前
Java中的控制流语句:if、switch、for、foreach、while、do-while
java·开发语言·python
bbblys1 小时前
B4172 学习计划 题解
算法·动态规划