在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适合更复杂的数据交互场景。
使用哪种方式,取决于进程之间的关系、通信频率与数据结构。熟悉各种方式的特点和实现机制,将为系统级或大型应用程序设计提供极大的帮助。