46、线程邮箱系统(C语言+多线程通信)

线程邮箱系统(C语言+多线程通信)

一、项目核心定位与优势

1. 核心目标

实现线程间的异步消息传递,让线程无需直接交互,通过「邮箱」中转消息,降低线程逻辑耦合,同时保证通信的线程安全和数据完整性。

2. 核心优势

  • 低耦合:线程仅需通过邮箱名发送/接收消息,无需知晓其他线程的ID、逻辑,修改单个线程不影响整体系统;
  • 易扩展:新增线程只需注册到邮箱系统,无需修改现有线程代码;
  • 线程安全 :通过互斥锁(pthread_mutex_t)保护临界资源,避免并发访问冲突;
  • 轻量级:基于原生C语言和内核链表实现,无第三方依赖,占用资源少;
  • 灵活适配:支持任意线程间一对一、一对多消息传递,消息格式可自定义。

二、核心架构与数据结构

1. 整体架构图(基于PDF梳理)

复制代码
┌─────────────────────────────────────────────────────────┐
│                   线程邮箱系统(MBS)                  │
│  ┌─────────────┐  ┌───────────┐  ┌─────────────────┐  │
│  │ 链表头(link_head) │ 互斥锁(mutex) │ 线程节点链表(LIST_DATA) │  │
│  └─────────────┘  └───────────┘  └─────────────────┘  │
│          │                │                │          │
│          ▼                ▼                ▼          │
│  ┌─────────────┐  ┌───────────┐  ┌─────────────────┐  │
│  │ 线程A(data) │ 线程B(show) │ 线程C(sock)     │  │
│  │ - 名字:data  │ - 名字:show │ - 名字:sock      │  │
│  │ - 线程ID:tid │ - 线程ID:tid │ - 线程ID:tid    │  │
│  │ - 消息队列   │ - 消息队列   │ - 消息队列       │  │
│  │ - 线程函数   │ - 线程函数   │ - 线程函数       │  │
│  └─────────────┘  └───────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘
        │                  │                  │
        ▼                  ▼                  ▼
┌─────────────┐  ┌───────────┐  ┌─────────────────┐
│ 发送消息    │  │ 接收消息    │  │ 接收消息        │
│ Send_msg("show", data) │ Recv_msg()      │ Recv_msg()      │
│ Send_msg("sock", data) │ 打印消息        │ 打印消息        │
└─────────────┘  └───────────┘  └─────────────────┘

2. 核心数据结构解析

(1)邮箱系统核心结构体(MBS)
c 复制代码
typedef struct mail_box_system {
    pthread_mutex_t mutex;  // 保护临界资源(消息队列、线程链表)
    struct list_head head;  // 线程节点链表头(复用Linux内核双向链表)
} MBS;
  • mutex:互斥锁,保证多线程并发访问时的线程安全;
  • head:双向链表头,管理所有注册到邮箱系统的线程节点。
(2)线程节点结构体(LIST_DATA)

每个注册到邮箱系统的线程对应一个节点,存储线程核心信息:

c 复制代码
typedef struct thread_node {
    pthread_t tid;        // 线程ID
    char name[256];       // 线程唯一名称(用于消息寻址)
    LinkQue* lq;          // 线程专属消息队列(存储接收的消息)
    th_fun th;            // 线程函数指针
    struct list_head node; // 链表节点(接入MBS的head链表)
} LIST_DATA;
(3)消息结构体(MAIL_DATA)

线程间传递的消息格式,可根据需求扩展字段:

c 复制代码
typedef struct mail_data {
    pthread_t id_of_sender;   // 发送者线程ID
    char name_of_sender[256]; // 发送者线程名称
    pthread_t id_of_recver;   // 接收者线程ID
    char name_of_recver[256]; // 接收者线程名称
    char data[256];          // 消息正文(可扩展为任意数据类型)
} MAIL_DATA;
(4)链表队列结构体(LinkQue)

用于存储线程的消息,实现FIFO(先进先出)消息传递:

c 复制代码
typedef struct quenode {
    DATATYPE data;         // 消息数据(DATATYPE=MAIL_DATA)
    struct quenode *next; // 下一个消息节点
} LinkQueNode;

typedef struct _linkque {
    LinkQueNode *head;    // 队列头
    LinkQueNode *tail;    // 队列尾
    int clen;             // 队列中消息数量
} LinkQue;

三、核心功能实现(基于代码拆解)

整个系统的核心流程为:「创建邮箱系统 → 线程注册 → 消息发送/接收 → 销毁系统」,下面逐一解析关键功能的实现逻辑。

1. 初始化邮箱系统(create_mail_box_system)

c 复制代码
MBS* create_mail_box_system() {
    MBS* m_mbs = malloc(sizeof(MBS));
    if (NULL == m_mbs) { perror("malloc failed"); return NULL; }
    INIT_LIST_HEAD(&m_mbs->head);  // 初始化双向链表头
    pthread_mutex_init(&m_mbs->mutex, NULL); // 初始化互斥锁
    return m_mbs;
}
  • 核心作用:分配邮箱系统内存,初始化线程链表和互斥锁,为后续线程注册和消息传递做准备;
  • 关键技术:INIT_LIST_HEAD是Linux内核链表的初始化宏,将链表头的nextprev指针指向自身,形成空链表。

2. 线程注册(register_to_mail_system)

线程需先注册到邮箱系统,才能发送/接收消息:

c 复制代码
int register_to_mail_system(MBS* mbs, char name[], th_fun th) {
    // 1. 分配线程节点内存
    LIST_DATA* list_node = malloc(sizeof(LIST_DATA));
    if (NULL == list_node) { perror("malloc failed"); return 1; }
    // 2. 初始化节点信息(名称、消息队列)
    strcpy(list_node->name, name);
    list_node->lq = CreateLinkQue(); // 创建专属消息队列
    // 3. 将节点加入邮箱系统的线程链表
    list_add(&list_node->node, &mbs->head);
    // 4. 创建线程(执行传入的线程函数th)
    pthread_create(&list_node->tid, NULL, th, NULL);
    return 0;
}
  • 核心作用:为线程创建专属消息队列,将线程节点接入系统链表,并启动线程;
  • 关键技术:list_add宏将线程节点插入链表头之后,实现高效的节点添加。

3. 消息发送(send_msg)

线程通过接收者名称发送消息,无需知晓接收者线程ID:

c 复制代码
int send_msg(MBS* mbs, char* recvname, MAIL_DATA* data) {
    // 1. 查找发送者自身节点(通过当前线程ID)
    LIST_DATA* myself = find_node_byid(mbs, pthread_self());
    if (NULL == myself) { fprintf(stderr, "find sender failed"); return 1; }
    // 2. 填充消息的发送者信息
    data->id_of_sender = pthread_self();
    strcpy(data->name_of_sender, myself->name);
    // 3. 查找接收者节点(通过接收者名称)
    LIST_DATA* recver = find_node_byname(mbs, recvname);
    if (NULL == recver) { fprintf(stderr, "find recver failed"); return 1; }
    // 4. 填充消息的接收者信息
    data->id_of_recver = recver->tid;
    strcpy(data->name_of_recver, recver->name);
    // 5. 加锁,将消息入队(线程安全)
    pthread_mutex_lock(&mbs->mutex);
    EnterLinkQue(recver->lq, data); // 消息入接收者队列
    pthread_mutex_unlock(&mbs->mutex);
    return 0;
}
  • 核心作用:封装消息的发送者和接收者信息,将消息安全加入接收者的消息队列;
  • 线程安全:通过pthread_mutex_lock/unlock保护消息入队操作,避免多线程并发写入冲突。

4. 消息接收(recv_msg)

线程从自身的消息队列中读取消息,实现异步接收:

c 复制代码
int recv_msg(MBS* mbs, MAIL_DATA* data) {
    // 1. 查找当前线程的节点
    LIST_DATA* myself = find_node_byid(mbs, pthread_self());
    if (NULL == myself) { fprintf(stderr, "find self failed"); return 1; }
    // 2. 加锁,读取队列头部消息
    pthread_mutex_lock(&mbs->mutex);
    MAIL_DATA* tmp = GetHeadLinkQue(myself->lq);
    if (NULL == tmp) { // 队列空,解锁返回
        pthread_mutex_unlock(&mbs->mutex);
        return 1;
    }
    // 3. 复制消息到接收缓冲区,从队列中移除消息
    memcpy(data, tmp, sizeof(MAIL_DATA));
    QuitLinkQue(myself->lq); // 消息出队
    pthread_mutex_unlock(&mbs->mutex);
    return 0;
}
  • 核心作用:从线程专属队列中读取消息,实现FIFO顺序处理;
  • 无消息处理:队列空时返回1,线程可通过循环+sleep重试,避免忙等。

5. 系统销毁与资源释放(destroy_mail_box_system)

c 复制代码
void destroy_mail_box_system(MBS* mbs) {
    LIST_DATA *pos, *q;
    // 遍历所有线程节点,删除并释放资源
    list_for_each_entry_safe(pos, q, &mbs->head, node) {
        list_del(&pos->node); // 从链表中删除节点
        free(pos);           // 释放节点内存
    }
    return;
}
  • 关键技术:list_for_each_entry_safe是内核链表的安全遍历宏,通过临时变量q保存下一个节点,避免删除当前节点后链表断裂。

四、实战使用示例(基于main.c)

下面通过一个完整的示例,展示线程邮箱系统的使用流程:创建系统→注册线程→发送/接收消息。

1. 线程函数实现

(1)数据生成线程(data_th)

周期性生成模拟传感器数据,发送给showsock线程:

c 复制代码
void* data_th(void* arg) {
    srand(time(NULL));
    MAIL_DATA data;
    while (1) {
        // 生成30.00~39.99的模拟数据
        int num = rand() % 1000 + 3000;
        float tmp = num / 100.0;
        bzero(&data, sizeof(data));
        sprintf(data.data, "temp:%.2f°C", tmp); // 消息正文
        // 发送消息给"show"线程
        send_msg(g_mbs, "show", &data);
        sleep(rand() % 3); // 随机休眠0~2秒
        // 发送消息给"sock"线程
        send_msg(g_mbs, "sock", &data);
        sleep(rand() % 2); // 随机休眠0~1秒
    }
    return NULL;
}
(2)消息展示线程(show_th)

循环接收消息并打印:

c 复制代码
void* show_th(void* arg) {
    MAIL_DATA data;
    while (1) {
        bzero(&data, sizeof(data));
        int ret = recv_msg(g_mbs, &data);
        if (1 == ret) { sleep(1); continue; } // 无消息时休眠
        // 打印接收的消息
        printf("[show线程] 接收来自%s的消息:%s\n", data.name_of_sender, data.data);
    }
    return NULL;
}
(3)网络发送线程(sock_th)

模拟将消息通过网络发送(此处简化为打印):

c 复制代码
void* sock_th(void* arg) {
    MAIL_DATA data;
    while (1) {
        bzero(&data, sizeof(data));
        int ret = recv_msg(g_mbs, &data);
        if (1 == ret) { sleep(1); continue; } // 无消息时休眠
        // 模拟网络发送
        printf("[sock线程] 接收来自%s的消息(待发送网络):%s\n", data.name_of_sender, data.data);
    }
    return NULL;
}

2. 主函数(程序入口)

c 复制代码
MBS* g_mbs; // 全局邮箱系统指针(方便线程函数访问)
int main(int argc, char** argv) {
    // 1. 创建邮箱系统
    g_mbs = create_mail_box_system();
    // 2. 注册3个线程:show、sock、data
    register_to_mail_system(g_mbs, "show", show_th);
    register_to_mail_system(g_mbs, "sock", sock_th);
    register_to_mail_system(g_mbs, "data", data_th);
    // 3. 等待所有线程结束(阻塞主线程)
    wait_all_end(g_mbs);
    // 4. 销毁邮箱系统,释放资源
    destroy_mail_box_system(g_mbs);
    return 0;
}

3. 编译与运行

(1)编译命令(需链接pthread库)
bash 复制代码
gcc main.c mailbox.c linkque.c -o thread_mailbox -lpthread
(2)运行结果示例
复制代码
[show线程] 接收来自data的消息:temp:32.56°C
[sock线程] 接收来自data的消息(待发送网络):temp:32.56°C
[show线程] 接收来自data的消息:temp:37.12°C
[sock线程] 接收来自data的消息(待发送网络):temp:35.89°C
...

五、设计亮点与技术细节

1. 复用Linux内核链表(list.h)

  • 优势:内核链表是经过验证的高效数据结构,支持快速插入、删除、遍历,无需手动实现链表操作;
  • 关键宏:list_add(添加节点)、list_del(删除节点)、list_for_each_entry_safe(安全遍历),简化线程节点管理。

2. 线程安全设计

  • 互斥锁(pthread_mutex_t):保护线程链表和消息队列的并发访问,避免多线程同时修改导致的数据错乱;
  • 消息队列隔离:每个线程拥有独立的消息队列,避免消息混淆,同时减少锁竞争。

3. 低耦合设计

  • 线程寻址:通过线程名称而非ID查找接收者,线程ID由系统分配,名称更易维护;
  • 消息封装:MAIL_DATA结构体统一消息格式,发送者和接收者无需关心对方的实现细节;
  • 扩展灵活:新增线程只需调用register_to_mail_system,无需修改现有线程代码。

六、扩展方向与适用场景

1. 功能扩展

  • 消息优先级:在MAIL_DATA中添加priority字段,消息队列按优先级排序,支持紧急消息优先处理;
  • 超时接收:扩展recv_msg函数,支持设置超时时间,避免线程无限阻塞;
  • 动态注销:新增unregister_from_mail_system函数,支持线程运行时退出邮箱系统;
  • 消息回执:添加消息发送成功确认机制,确保消息被接收。

2. 适用场景

  • 嵌入式系统:传感器数据采集线程→数据处理线程→网络发送线程的消息传递;
  • 多模块协作:如Web服务器中,请求接收线程→业务处理线程→响应发送线程;
  • 日志系统:多个业务线程向日志线程发送日志消息,统一打印或写入文件。
相关推荐
源远流长jerry28 分钟前
curl、ping、iptables、iperf、tcpdump解析
网络·网络协议·测试工具·ip·tcpdump
米羊12139 分钟前
OA 系统防护与渗透测试(上)
网络·安全
林疏safe1 小时前
应急演练剧本
网络
王正南1 小时前
kali-linux 虚拟机连接安卓模拟器
android·linux·运维·虚拟机连接模拟器·安卓模拟器,linux虚拟机
吳所畏惧2 小时前
Linux环境/麒麟V10SP3下离线安装Redis、修改默认密码并设置Redis开机自启动
linux·运维·服务器·redis·中间件·架构·ssh
yueguangni2 小时前
sysstat 版本 10.1.5 是 CentOS 7 的默认版本,默认情况下确实不显示 %wait 字段。需要升级到新版sysstat
linux·运维·centos
manuel_897573 小时前
六 系统安全
网络·数据库·系统安全
无忧智库3 小时前
深度解读《某低空经济试验区“十五五”通用航空机场与无人机物流网络初步设计方案》:构建未来低空经济数字底座的全景蓝图
网络·无人机
萧曵 丶3 小时前
Linux 业务场景常用命令详解
linux·运维·服务器
Arwen3034 小时前
SSL 加密证书助力企业构建安全的网络环境
网络·网络协议·tcp/ip·安全·php·ssl