引言
在操作系统中,进程间通信(IPC)是协调多进程协作的核心技术。不同的通信方式有各自的适用场景和性能特点。本文将通过代码实例详细讲解管道、消息队列、共享内存、信号量、信号和套接字等6种IPC方式,帮助开发者选择合适方案。
一、管道(Pipes)
1.1 匿名管道(无名管道)
特点:
- 仅限父子进程或兄弟进程间通信
- 半双工通信,数据单向流动
- 内核缓冲区大小有限(通常4KB)
C++代码示例:
#include <unistd.h>
#include <sys/wait.h>
#include <cstring>
int main() {
int pipe_fd[2];
char buffer[100];
if (pipe(pipe_fd) == -1) { // 创建管道
perror("pipe");
return 1;
}
pid_t pid = fork();
if (pid == 0) { // 子进程读数据
close(pipe_fd[1]); // 关闭写端
read(pipe_fd[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
close(pipe_fd[0]);
} else { // 父进程写数据
close(pipe_fd[0]); // 关闭读端
const char* msg = "Hello from parent!";
write(pipe_fd[1], msg, strlen(msg)+1);
close(pipe_fd[1]);
wait(NULL); // 等待子进程
}
return 0;
}
关键点:pipe()
返回两个文件描述符,pipe_fd[0]
读,pipe_fd[1]
写
1.2 命名管道(FIFO)
特点:
- 通过文件系统路径标识
- 无关进程可通信
- 支持多读多写
C++代码示例:
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
const char* fifo_path = "/tmp/my_fifo";
mkfifo(fifo_path, 0666); // 创建命名管道
int fd = open(fifo_path, O_WRONLY);
write(fd, "Hello FIFO!", 12);
close(fd);
return 0;
}
注意:需先运行mkfifo
创建管道文件,读写方需分别以O_WRONLY
和O_RDONLY
打开
二、消息队列(Message Queue)
特点:
- 消息按类型存储和读取
- 支持异步通信
- 存在消息长度和队列容量限制
C++线程安全队列实现:
#include <queue>
#include <mutex>
#include <condition_variable>
template<typename T>
class SafeQueue {
private:
std::queue<T> queue;
std::mutex mtx;
std::condition_variable cv;
public:
void push(const T& item) {
std::lock_guard<std::mutex> lock(mtx);
queue.push(item);
cv.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]{ return !queue.empty(); });
T item = queue.front();
queue.pop();
return item;
}
};
说明:使用std::mutex
和std::condition_variable
实现同步
三、共享内存(Shared Memory)
特点:
- 零拷贝,直接操作内存
- 需配合信号量同步
- 适用于大数据量传输
POSIX共享内存示例:
#include <sys/mman.h>
#include <fcntl.h>
#include <cstring>
int main() {
const char* name = "/my_shm";
int fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ftruncate(fd, 1024); // 设置内存大小
void* ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
strcpy(static_cast<char*>(ptr), "Shared data"); // 写入数据
munmap(ptr, 1024);
close(fd);
shm_unlink(name); // 删除共享内存对象
return 0;
}
关键函数:shm_open
创建对象,mmap
映射内存
四、信号量(Semaphore)
作用:
- 控制对共享资源的访问
- 实现进程间互斥与同步
POSIX信号量示例:
#include <semaphore.h>
#include <fcntl.h>
int main() {
sem_t* sem = sem_open("/my_sem", O_CREAT, 0644, 1); // 初始值1
sem_wait(sem); // P操作:获取信号量
// 临界区代码
sem_post(sem); // V操作:释放信号量
sem_close(sem);
sem_unlink("/my_sem");
return 0;
}
注意:命名信号量通过路径标识,可用于无关进程
五、信号(Signal)
用途:
- 通知进程特定事件发生
- 如
SIGINT
(Ctrl+C)、SIGTERM
(终止信号)
信号处理示例:
#include <csignal>
#include <unistd.h>
void handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main() {
signal(SIGUSR1, handler); // 注册信号处理函数
raise(SIGUSR1); // 发送信号
return 0;
}
扩展:kill()
函数可向指定进程发送信号
六、套接字(Socket)
特点:
- 支持跨网络通信
- 分为TCP(可靠)和UDP(高效)
TCP套接字示例:
// 服务端
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr{AF_INET, htons(8080), INADDR_ANY};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
int client_fd = accept(sockfd, NULL, NULL);
send(client_fd, "Hello client!", 13, 0);
close(client_fd);
close(sockfd);
return 0;
}
流程:socket()
→bind()
→listen()
→accept()
→send()/recv()
总结与选择建议
通信方式 | 适用场景 | 性能 | 复杂度 |
---|---|---|---|
管道 | 父子进程简单通信 | 中 | 低 |
消息队列 | 异步通信,松散耦合 | 中 | 中 |
共享内存 | 大数据量、实时性要求高 | 高 | 高 |
信号量 | 资源访问控制 | 高 | 中 |
套接字 | 跨网络通信 | 可变 | 高 |
开发建议:优先考虑共享内存+信号量组合处理高性能场景,简单通信可使用管道或信号。