41.多进程-管道
约 976 字大约 3 分钟
进程间通信
1.管道(pipe):单向通信,适用于父子进程。
2.消息队列(message queue):多进程可读写,类似消息队列。
3.共享内存(shared memory):最快的方式,多个进程共享一块内存。
4.信号量(semaphore):用于同步多个进程,避免资源竞争。
5.套接字(socket):适用于不同主机间的进程通信。
管道通信
父进程写,子进程读
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid > 0)
{
// 父进程
close(fd[0]); // 关闭读端
char msg[] = "Hello from parent!";
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]); // 关闭写端
}
else if (pid == 0)
{
// 子进程
close(fd[1]); // 关闭写端
char buffer[100];
read(fd[0], buffer, sizeof(buffer));
printf("子进程收到消息: %s\n", buffer);
close(fd[0]); // 关闭读端
}
return 0;
}
子进程写,父进程读
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid > 0) { // 父进程
close(fd[1]); // 关闭写端
char buffer[100];
read(fd[0], buffer, sizeof(buffer));
printf("父进程收到: %s\n", buffer);
close(fd[0]); // 关闭读端
} else if (pid == 0) { // 子进程
close(fd[0]); // 关闭读端
char msg[] = "Hello from child!";
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]); // 关闭写端
}
return 0;
}
管道通信的常见问题
(1) 死锁
如果读端未关闭,写端等待,可能会卡住。
解决方案:
关闭未使用的端口
非阻塞读写
(2) 进程顺序问题
fork() 之后,父子进程可能并行执行,需要同步。
可以用 sleep() 或 wait() 让父进程等待子进程执行完。
进程通信 消息队列
发送消息进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define MSG_KEY 1234
struct msgbuf {
long mtype;
char mtext[128];
};
int main() {
int msgid = msgget(MSG_KEY, IPC_CREAT | 0666); // 创建消息队列
if (msgid == -1) {
perror("msgget");
exit(1);
}
struct msgbuf msg;
msg.mtype = 1; // 消息类型
strcpy(msg.mtext, "Hello from sender!");
// 发送消息
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(1);
}
printf("消息发送成功: %s\n", msg.mtext);
return 0;
}
接收消息进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 1234
struct msgbuf {
long mtype;
char mtext[128];
};
int main() {
int msgid = msgget(MSG_KEY, 0666); // 获取消息队列
if (msgid == -1) {
perror("msgget");
exit(1);
}
struct msgbuf msg;
// 接收消息
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("收到消息: %s\n", msg.mtext);
return 0;
}
消息队列创建后不会自动销毁,需要手动删除
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_KEY 1234
int main() {
int msgid = msgget(MSG_KEY, 0666);
if (msgid == -1) {
perror("msgget");
exit(1);
}
// 删除消息队列
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("消息队列已删除\n");
return 0;
}
共享内存
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#define SHM_KEY 1234
int main() {
int shmid = shmget(SHM_KEY, 1024, IPC_CREAT | 0666);
char *shmptr = (char *)shmat(shmid, NULL, 0);
if (fork() == 0) { // 子进程
sleep(1); // 等待父进程写入
printf("子进程读取: %s\n", shmptr);
shmdt(shmptr);
} else { // 父进程
strcpy(shmptr, "Hello from parent!");
wait(NULL); // 等待子进程结束
shmdt(shmptr);
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
}
return 0;
}
信号量
📌 sem_p() 阻塞进程,等待信号量。
📌 sem_v() 释放信号量,让子进程继续执行。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#define SEM_KEY 1234 // 信号量 key
void sem_p(int semid) { // P 操作(等待)
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
}
void sem_v(int semid) { // V 操作(信号量+1)
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
}
int main() {
int semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
semctl(semid, 0, SETVAL, 0); // 初始值 0,子进程等待父进程
if (fork() == 0) { // 子进程
printf("子进程等待数据...\n");
sem_p(semid); // 等待父进程的信号
printf("子进程收到信号,开始读取数据!\n");
exit(0);
} else { // 父进程
sleep(2);
printf("父进程写入数据完成!\n");
sem_v(semid); // 释放信号,子进程可以继续执行
wait(NULL);
semctl(semid, 0, IPC_RMID); // 删除信号量
}
return 0;
}