面试题答案
一键面试资源管理以避免资源泄漏和竞争条件
- 资源分配与释放策略:
- 显式分配与释放:在进程开始使用资源时,明确分配资源(如通过
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,子进程在访问共享文件描述符前获取信号量,访问结束后释放信号量。
#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;
}
对共享文件描述符进行读写操作时保证数据一致性
- 使用同步机制:
- 基于信号量:如上述信号量示例代码,子进程和父进程在对共享文件描述符
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
函数获取文件锁,操作完成后释放锁,从而保证了数据一致性。