Linux下进程间通信方式详解

在Linux系统中,进程间通信(IPC) 是多个进程之间交换数据或状态的机制。通过 IPC,进程可以协作完成复杂的任务,共享资源,或者同步执行。选择合适的方式来交换信息或同步行为,是开发高性能、稳定性高的系统服务或应用程序的关键


一、管道(Pipe)

作用 :用于在进程之间传递数据,实现一进一出的单向通信。 分类

  • 无名管道:只能用于有亲缘关系的进程,如父子进程
  • 有名管道(FIFO):可以跨进程通信,需要提前创建文件

优点 :实现简单,适合小型数据通信。 缺点:无法避免阻塞,效率一般。

cpp 复制代码
#include <unistd.h>
#include <iostream>

int main() {
    int pipefd[2]; // 父子通信:0是读端,1是写端
    pid_t pid;

    if (pipe(pipefd) == -1) {
        std::cerr << "Pipe创建失败" << std::endl;
        return 1;
    }

    pid = fork();

    if (pid < 0) {
        std::cerr << "创建子进程失败" << std::endl;
        return 1;
    } else if (pid > 0) { // 父进程
        close(pipefd[0]);
        write(pipefd[1], "Hello from parent", 19);
        close(pipefd[1]);
    } else { // 子进程
        close(pipefd[1]);
        char buffer[100];
        read(pipefd[0], buffer, sizeof(buffer));
        std::cout << "子进程接收到:" << buffer << std::endl;
        close(pipefd[0]);
    }

    return 0;
}

二、消息队列(Message Queue)

作用 :进程间传递结构化的消息,较管道更灵活。 分类

  • 系统V消息队列:稳定,支持类型和优先级
  • POSIX消息队列:提供了更现代化和高级的功能

优点 :结构清晰,支持多个进程并发访问。 缺点:相比共享内存,性能较低。

cpp 复制代码
#include <sys/msg.h>
#include <iostream>
#include <cstring>

struct msgbuff {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key = ftok("tempfile", 1); // 生成唯一键值用于队列
    int msqid = msgget(key, 0666 | IPC_CREAT);

    if (msqid == -1) {
        std::cerr << "消息队列创建失败" << std::endl;
        return 1;
    }

    msgbuff msg;
    msg.mtype = 1;
    strcpy(msg.mtext, "消息队列示例");

    if (msgsnd(msqid, &msg, sizeof(msg), 0) == -1) {
        std::cerr << "发送消息失败" << std::endl;
        return 1;
    }

    if (msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
        std::cerr << "接收消息失败" << std::endl;
        return 1;
    }

    std::cout << "接收到消息:" << msg.mtext << std::endl;

    msgctl(msqid, IPC_RMID, NULL); // 删除消息队列
    return 0;
}

三、信号量(Semaphore)

作用 :用于控制共享资源的访问,保障同步精确性。通常与共享内存配合使用。 分类

  • 系统V信号量:通过键值分配 ID,适合多个信号量的管理
  • POSIX信号量:API更简洁,支持线程和进程同步

优点 :可控制临界区访问,用于进程间的资源协调。 缺点:使用不当容易引发死锁,应谨慎处理。

cpp 复制代码
#include <sys/sem.h>
#include <iostream>

int main() {
    key_t key = ftok("sem_temp", 1);
    int semid = semget(key, 1, 0666 | IPC_CREAT);

    if (semid == -1) {
        std::cerr << "信号量创建失败" << std::endl;
        return 1;
    }

    union semun {
        int val;
        void *arg;
    } sem_value;
    sem_value.val = 1;
    if (semctl(semid, 0, SETVAL, sem_value) < 0) {
        std::cerr << "信号量初始化失败" << std::endl;
        return 1;
    }

    struct sembuf op;
    op.sem_num = 0;
    op.sem_op = -1;
    op.sem_flg = 0;
    if (semop(semid, &op, 1) < 0) {
        std::cerr << "信号量获取失败" << std::endl;
        return 1;
    }

    std::cout << "占用资源中..." << std::endl;

    op.sem_op = 1;
    if (semop(semid, &op, 1) < 0) {
        std::cerr << "信号量释放失败" << std::endl;
        return 1;
    }

    if (semctl(semid, 0, IPC_RMID, sem_value) < 0)
        std::cerr << "信号量销毁失败" << std::endl;

    return 0;
}

四、共享内存(Shared Memory)

作用:多个进程共享一块内存区域,提升通信效率且为最快速的 IPC 方法。

优点 :速度快,适合需要频繁通信的场景。 缺点:没有内置同步机制,需结合信号量等使用,避免数据竞争。


五、信号(Signal)

作用 :用于异步通知,比如触发进程退出、处理中断等。 常见信号

  • SIGINT:中断信号(如按下 Ctrl+C)
  • SIGTERM:终止信号
  • SIGCHLD:子进程相关事件
  • SIGUSR1 / SIGUSR2:用户自定义信号

优点 :轻量级,适合进程行为控制。 缺点:信息量少,无法传递数据,不支持精细通信。

示例:处理 SIGINT 信号

cpp 复制代码
#include <csignal>
#include <iostream>

void handle_signal(int sig) {
    std::cout << "捕捉到信号 " << sig << " 程序将退出" << std::endl;
    exit(0);
}

int main() {
    signal(SIGINT, handle_signal); // 注册信号处理

    std::cout << "运行中... 请按下 Ctrl+C 看效果\n";
    while (true) {
        // 循环等待中断
    }

    return 0;
}

六、套接字(Socket)

作用 :基于网络协议实现在本地或远程进程之间通信。 常用类型

  • 流式套接字(TCP):面向连接、丢包率低、适用于可靠通信
  • 数据报套接字(UDP):无连接,丢包风险稍高,但延时小

本地通信常用方式 :使用 Unix 域套接字 AF_UNIX,比 TCP 更高效。


七、内存映射文件(Memory-Mapped File / mmap)

作用:将文件直接映射到内存中,允许多个进程访问同一文件内容。

优点 :适合高频访问文件内容,减少 I/O 操作。 缺点:必须兼顾进程同步问题,否则数据一致性难以保证。

cpp 复制代码
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <cstdlib>

int main() {
    int fd = open("data.txt", O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        std::cerr << "无法打开文件" << std::endl;
        return 1;
    }

    // 将文件映射到内存
    const int filesize = 100;
    char* data = (char*)mmap(
        NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (data == MAP_FAILED) {
        std::cerr << "内存映射失败" << std::endl;
        close(fd);
        return 1;
    }

    strcpy(data, "共享文件内容");

    std::cout << "写入完成,当前共享内容: " << data << std::endl;

    // 解除映射
    munmap(data, filesize);
    close(fd);

    return 0;
}

八、 总结

选择进程间通信方式时需要依据具体需求,以下为常见的场景对应建议:

通信需求 推荐方式
本地父子进程通信 无名管道 / 信号 / 共享内存(结合信号量)
多个独立进程通信 有名管道 / 消息队列 / 共享内存(配合同步)
控制资源访问、微型同步 信号量 / 互斥量
通信数据量大且要求高性能 共享内存 + 同步机制(互斥量、信号量)
多进程任务协同、远程通信等 套接字(网络套接字或 Unix 域套接字)
结构清晰的消息传递 消息队列 / gRPC / Thrift
高频访问文件 内存映射文件(mmap) + 同步机制

在Linux中,C++的进程间通信可以根据实际需要选用合适的方式。无名管道和有名管道适用于简单的进程通信,而消息队列适合处理结构化的消息传递。共享内存速度快,适合需要高性能的场景,但需要采用同步机制防止并发冲突。信号则适合触发生搞控制或通知。套接字和mmap适合更复杂的数据交互场景。

使用哪种方式,取决于进程之间的关系、通信频率与数据结构。熟悉各种方式的特点和实现机制,将为系统级或大型应用程序设计提供极大的帮助。

相关推荐
JuiceFS3 分钟前
合合信息:基于 JuiceFS 构建统一存储,支撑 PB 级 AI 训练
运维·后端
调试人生的显微镜9 分钟前
iOS 文件深度调试实战 查看用户文件 App 沙盒 系统文件与日志全指南
后端
aiopencode11 分钟前
没有 Mac,如何上架 iOS App?跨平台团队的全流程实践指南
后端
天天摸鱼的java工程师13 分钟前
如何防止重复提交订单?
java·后端·面试
星星电灯猴16 分钟前
Charles 抓不到包怎么办?详解原因与多维解决方案
后端
慕尘_19 分钟前
对于未来技术的猜想:Manus as a Service
前端·后端
秋水丶秋水26 分钟前
Python常见异常和处理方案
后端·python
程序无bug26 分钟前
pring Boot监控方案
java·后端
荔枝爱编程27 分钟前
高性能企业级消息中心架构实现与分享(二)
后端·消息队列·rocketmq
莹莹啦29 分钟前
Java 21 核心特性全景解析:LTS 版本的革命性升级
后端