You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实时系统中基于C语言的线程安全数据传输架构设计问询

问题:多线程主程序与设备通信模型的线程安全接口设计

问题背景

我维护一个设备通信模型,内部有两个线程:通信线程(负责设备读写)和日志线程(负责记录结果),模型内部用互斥锁保证线程安全,目前没有问题。

现在主程序是多线程架构,需要调用该模型进行数据收发,同时要保护主程序内部的buffer,避免多线程冲突。

如果用单一全局互斥锁,会破坏模块化设计,还会让逻辑变得复杂。请问该怎么在通信模型和主程序之间设计线程安全的接口?

示例代码

设备模型头文件(device.h)

#ifndef DEVICE_H
#define DEVICE_H

struct d_in {
    int x;
    int y;
    int z;
};

struct d_out {
    int x;
    int y;
    int z;
};

struct device {
    struct d_in input;
    struct d_out output;
    pthread_mutex_t mutex;
    sem_t sem_data;
};

void *communication_thread(void *arg);
void *logging_thread(void *arg);

void _init_device();
void _read_input(struct d_in *input);
void _write_output(const struct d_out *output);

// 我期望的接口
void transfer_data(struct *dev);

#endif /* DEVICE_H */

设备模型实现文件(device.c)

#include "device.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "special_card_for_reading_from_device_.h"

static struct device dev;

void init_device() {
    dev.input.x = 0;
    dev.input.y = 0;
    dev.input.z = 0;
    dev.output.x = 0;
    dev.output.y = 0;
    dev.output.z = 0;
    pthread_mutex_init(&dev.mutex, NULL);
    sem_init(&sem_data, NULL, 0); // 修正原代码参数错误
}

void read_input() {
    pthread_mutex_lock(&dev.mutex);
    read_fucntion(&dev.input);
    pthread_mutex_unlock(&dev.mutex);
}

void write_output() {
    pthread_mutex_lock(&dev.mutex);
    write_fucntion(&dev.output);
    pthread_mutex_unlock(&dev.mutex);
}

void *communication_thread(void *arg) {
    while (1) {
        read_input();
        write_output();
        sem_post(&sem_data);
        sleep(1); // 模拟通信延迟
    }
    return NULL;
}

void *logging_thread(void *arg) {
    FILE *fp = fopen("log.txt", "w");
    if (fp == NULL) {
        perror("打开日志文件失败");
        exit(EXIT_FAILURE);
    }

    while (1) {
        sem_wait(&sem_data);
        pthread_mutex_lock(&dev.mutex);
        fprintf(fp, "输入: %d %d %d\n", dev.input.x, dev.input.y, dev.input.z);
        fprintf(fp, "输出: %d %d %d\n\n", dev.output.x, dev.output.y, dev.output.z);
        pthread_mutex_unlock(&dev.mutex);
        sleep(2); // 日志记录间隔
    }

    fclose(fp);
    return NULL;
}

主程序多线程代码

void *transfer(void *arg) {
    while (1) {
        // 如何保护这段代码?
        transfer(&buffer_dev_data);
        //////////////////////////
        sleep(1);
    }
    return NULL;
}

void *another_thread(void *arg) {
    while (1) {
        // 如何保护这段代码?
        operations_on_bufffer(buffer_dev_data);
        //////////////////////////
        sleep(1);
    }
    return NULL;
}

额外疑问

用两个互斥锁保护同一临界区会不会导致死锁?比如在transfer函数内部用一个互斥锁,外部再加一个互斥锁,逻辑如下:

mutex_lock(&mut_B);
mutex_lock(&mut_A);
// 临界区代码
mutex_unlock(&mut_A);
mutex_unlock(&mut_B);

这种写法有没有问题?

补充说明:程序基于RT-Linux实时内核开发,是实时系统,实际逻辑更复杂,还涉及线程优先级机制。


解决方案

一、设备模型层面:封装线程安全的接口

核心思路是把设备模型的内部状态完全封装,对外只暴露线程安全的操作接口,让主程序无需关心模型内部的锁机制,只需要调用接口即可。

1. 完善transfer_data接口的线程安全实现

修改接口定义明确功能,实现时利用模型内部互斥锁保证原子性:

// device.h 修正接口定义
void transfer_data(struct device *dev, const struct d_out *send_data, struct d_in *recv_data);

// device.c 实现接口
void transfer_data(struct device *dev, const struct d_out *send_data, struct d_in *recv_data) {
    pthread_mutex_lock(&dev->mutex);
    
    // 原子执行:写入待发送数据 + 读取设备输入数据
    if (send_data != NULL) {
        dev->output = *send_data;
        write_fucntion(&dev->output); // 直接调用底层函数,避免重复加锁
    }
    if (recv_data != NULL) {
        read_fucntion(&dev->input); // 直接调用底层函数
        *recv_data = dev->input;
    }
    
    pthread_mutex_unlock(&dev->mutex);
}

主程序调用该接口时无需自行加锁,接口内部已保证线程安全,且与模型内部线程的锁逻辑一致,不会产生冲突。

2. 隔离模型内部状态

将struct device的成员设置为私有(比如在device.c中用static,或采用opaque指针技巧),禁止主程序直接访问,彻底隔离锁逻辑。

二、主程序层面:独立保护自身buffer

主程序内部的buffer_dev_data属于自身状态,单独用一把互斥锁保护,与设备模型的锁完全分离,遵循模块化原则:

// 主程序定义自身互斥锁
pthread_mutex_t buffer_mutex;

void init_main() {
    pthread_mutex_init(&buffer_mutex, NULL);
}

void *transfer(void *arg) {
    while (1) {
        struct d_in recv_data;
        struct d_out send_data = {1,2,3}; // 示例数据
        
        pthread_mutex_lock(&buffer_mutex);
        // 从buffer读取待发送数据到send_data
        // ...
        pthread_mutex_unlock(&buffer_mutex);
        
        // 调用设备模型的线程安全接口
        transfer_data(&dev, &send_data, &recv_data);
        
        pthread_mutex_lock(&buffer_mutex);
        // 将recv_data写入buffer_dev_data
        buffer_dev_data.input = recv_data;
        pthread_mutex_unlock(&buffer_mutex);
        
        sleep(1);
    }
    return NULL;
}

void *another_thread(void *arg) {
    while (1) {
        pthread_mutex_lock(&buffer_mutex);
        // 安全操作buffer_dev_data
        operations_on_bufffer(buffer_dev_data);
        pthread_mutex_unlock(&buffer_mutex);
        
        sleep(1);
    }
    return NULL;
}

主程序锁仅负责保护自身buffer,设备模型锁仅负责保护内部状态,两者职责清晰,互不干扰。

三、关于双重互斥锁的疑问

你提到的嵌套加锁写法本身不会死锁,但存在明显风险:

  1. 死锁隐患:若其他代码存在反向加锁顺序(先锁mut_A再锁mut_B),会立即触发死锁;实时系统中不同优先级线程还可能引发优先级反转。
  2. 性能损耗:双重加锁会增加锁开销,对于实时系统,不必要的锁会降低响应速度。
  3. 逻辑冗余:如果transfer内部已用mut_A保护临界区,外部加mut_B属于重复保护,除非mut_B是用来保护主程序自身状态(如buffer),此时需严格固定加锁顺序。

更推荐的方式是拆分锁的职责,避免嵌套加锁,简化逻辑。

四、实时系统额外注意事项

针对RT-Linux环境,需额外关注:

  • 使用优先级继承互斥锁(PTHREAD_MUTEX_PRIO_INHERIT)初始化,避免优先级反转。
  • 尽量缩短临界区执行时间,防止高优先级线程被长时间阻塞。
  • 替换sleep()为RT-Linux提供的nanosleep()或实时定时器,保证实时性。

内容的提问来源于stack exchange,提问作者Ahmad Ibrahem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:12:04