跳至主要內容

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;
}