MST

星途 面试题库

面试题:C语言Linux匿名管道读写优化之多进程协作与同步

在一个使用Linux C语言匿名管道进行通信的多进程程序中,有多个写进程向同一个管道写数据,一个读进程从管道读数据。请描述如何通过信号量机制来优化读写操作,以避免数据混乱和进程同步问题。请给出关键代码片段,并解释信号量的初始化、P操作和V操作在该场景中的作用。
32.5万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

信号量机制优化读写操作

  1. 避免数据混乱和进程同步问题的思路
    • 使用信号量来控制对管道的访问。在写进程向管道写数据前,先获取信号量,确保同一时间只有一个写进程能向管道写入数据,避免数据混乱。读进程在从管道读数据前也获取信号量,确保读操作与写操作同步。
  2. 关键代码片段
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <fcntl.h>

// 定义信号量操作函数
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

void semaphore_P(int semid) {
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = SEM_UNDO;
    semop(semid, &sem_op, 1);
}

void semaphore_V(int semid) {
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = 1;
    sem_op.sem_flg = SEM_UNDO;
    semop(semid, &sem_op, 1);
}

int main() {
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    key_t key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    int semid = semget(key, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    union semun sem_set;
    sem_set.val = 1;
    if (semctl(semid, 0, SETVAL, sem_set) == -1) {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    // 创建写进程
    pid_t pid_writer = fork();
    if (pid_writer == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid_writer == 0) {
        close(pipefd[0]);
        semaphore_P(semid);
        write(pipefd[1], "Hello from writer", 16);
        semaphore_V(semid);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }

    // 创建读进程
    pid_t pid_reader = fork();
    if (pid_reader == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid_reader == 0) {
        close(pipefd[1]);
        semaphore_P(semid);
        char buffer[256];
        ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer));
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("Read: %s\n", buffer);
        }
        semaphore_V(semid);
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    }

    // 父进程等待子进程结束
    wait(NULL);
    wait(NULL);

    // 删除信号量
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("semctl IPC_RMID");
        exit(EXIT_FAILURE);
    }

    return 0;
}
  1. 信号量的初始化、P操作和V操作的作用
    • 初始化
      union semun sem_set;
      sem_set.val = 1;
      if (semctl(semid, 0, SETVAL, sem_set) == -1) {
          perror("semctl");
          exit(EXIT_FAILURE);
      }
      
      • 将信号量初始化为1,表示管道可以被一个进程访问。这是二值信号量,用于实现互斥访问管道。
    • P操作
      void semaphore_P(int semid) {
          struct sembuf sem_op;
          sem_op.sem_num = 0;
          sem_op.sem_op = -1;
          sem_op.sem_flg = SEM_UNDO;
          semop(semid, &sem_op, 1);
      }
      
      • 写进程或读进程在访问管道前调用semaphore_Psem_op.sem_op = -1表示将信号量的值减1。如果信号量的值为0,进程会阻塞,直到信号量的值变为1(即有其他进程释放了信号量),从而保证同一时间只有一个进程能访问管道。
    • V操作
      void semaphore_V(int semid) {
          struct sembuf sem_op;
          sem_op.sem_num = 0;
          sem_op.sem_op = 1;
          sem_op.sem_flg = SEM_UNDO;
          semop(semid, &sem_op, 1);
      }
      
      • 写进程或读进程在完成对管道的访问后调用semaphore_Vsem_op.sem_op = 1表示将信号量的值加1,允许其他进程获取信号量并访问管道。SEMU_UNDO保证在进程异常终止时,信号量能恢复到初始状态,避免死锁。