MST

星途 面试题库

面试题:C语言多进程编程的资源管理

在一个复杂的C语言多进程程序中,多个子进程可能同时竞争系统资源(如文件描述符、内存等),请阐述如何有效地管理这些资源以避免资源泄漏和竞争条件。请举例说明在多进程环境下,对共享文件描述符进行读写操作时如何保证数据一致性。
40.6万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

资源管理以避免资源泄漏和竞争条件

  1. 资源分配与释放策略
    • 显式分配与释放:在进程开始使用资源时,明确分配资源(如通过open获取文件描述符,malloc分配内存),并在使用完毕后,确保及时释放(如close关闭文件描述符,free释放内存)。例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd = open("test.txt", O_CREAT | O_WRONLY, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    // 使用文件描述符fd进行操作
    close(fd);
    return 0;
}
- **使用RAII(Resource Acquisition Is Initialization)思想**:虽然C语言没有像C++那样原生支持RAII,但可以通过封装函数和结构体来模拟。例如,定义一个结构体管理文件描述符,并在初始化和销毁结构体时进行文件的打开和关闭操作。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

typedef struct {
    int fd;
} FileResource;

FileResource* createFileResource(const char* filename) {
    FileResource* res = (FileResource*)malloc(sizeof(FileResource));
    if (!res) {
        return NULL;
    }
    res->fd = open(filename, O_CREAT | O_WRONLY, 0644);
    if (res->fd == -1) {
        free(res);
        return NULL;
    }
    return res;
}

void destroyFileResource(FileResource* res) {
    if (res) {
        close(res->fd);
        free(res);
    }
}
  1. 同步机制
    • 信号量:用于控制对共享资源的访问数量。例如,创建一个信号量,初始值为1,子进程在访问共享文件描述符前获取信号量,访问结束后释放信号量。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/sem.h>

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

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

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

int main() {
    int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        return 1;
    }
    union semun arg;
    arg.val = 1;
    if (semctl(semid, 0, SETVAL, arg) == -1) {
        perror("semctl");
        semctl(semid, 0, IPC_RMID);
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        semctl(semid, 0, IPC_RMID);
        return 1;
    } else if (pid == 0) {
        semaphoreP(semid);
        int fd = open("shared_file.txt", O_WRONLY | O_APPEND);
        if (fd != -1) {
            write(fd, "Child process write\n", 18);
            close(fd);
        }
        semaphoreV(semid);
        _exit(0);
    } else {
        semaphoreP(semid);
        int fd = open("shared_file.txt", O_WRONLY | O_APPEND);
        if (fd != -1) {
            write(fd, "Parent process write\n", 19);
            close(fd);
        }
        semaphoreV(semid);
        wait(NULL);
        semctl(semid, 0, IPC_RMID);
    }
    return 0;
}
- **互斥锁**:本质上也是一种特殊的二元信号量(值为0或1)。虽然C语言标准库没有原生互斥锁,但可以使用`pthread_mutex_t`(在支持POSIX线程的系统上)来实现进程间互斥(通过共享内存将互斥锁映射到多个进程空间)。不过这种方法在纯多进程环境下相对复杂,且依赖特定系统库。
- **文件锁**:使用`fcntl`函数设置文件锁,在对共享文件描述符进行读写操作前获取文件锁,操作完成后释放文件锁。例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    int fd = open("shared_file.txt", O_WRONLY | O_APPEND);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    struct flock fl;
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    if (fcntl(fd, F_SETLKW, &fl) == -1) {
        perror("fcntl");
        close(fd);
        return 1;
    }
    write(fd, "Process write\n", 13);
    fl.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &fl) == -1) {
        perror("fcntl");
    }
    close(fd);
    return 0;
}

对共享文件描述符进行读写操作时保证数据一致性

  1. 使用同步机制
    • 基于信号量:如上述信号量示例代码,子进程和父进程在对共享文件描述符shared_file.txt进行写操作前,先获取信号量,确保同一时间只有一个进程能进行写操作,避免数据混乱。
    • 基于文件锁:通过fcntl设置文件锁,在写操作前获取写锁(F_WRLCK),操作完成后释放锁(F_UNLCK)。这样可以保证在获取锁期间,其他进程无法对文件进行写操作,从而保证数据一致性。例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

void writeToFile(int fd, const char* msg) {
    struct flock fl;
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    if (fcntl(fd, F_SETLKW, &fl) == -1) {
        perror("fcntl");
        return;
    }
    write(fd, msg, strlen(msg));
    fl.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &fl) == -1) {
        perror("fcntl");
    }
}

int main() {
    int fd = open("shared_file.txt", O_WRONLY | O_APPEND);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        close(fd);
        return 1;
    } else if (pid == 0) {
        writeToFile(fd, "Child process write\n");
        _exit(0);
    } else {
        writeToFile(fd, "Parent process write\n");
        wait(NULL);
    }
    close(fd);
    return 0;
}

在这个示例中,无论是父进程还是子进程,在对共享文件shared_file.txt进行写操作前,都通过writeToFile函数获取文件锁,操作完成后释放锁,从而保证了数据一致性。