面试题答案
一键面试系统资源分配
- 预分配文件描述符: 提前打开并分配好一些常用的文件描述符,如日志文件、配置文件等。这样在子进程启动时就无需再进行打开操作,减少I/O开销。例如,主进程可以在fork之前打开日志文件:
int log_fd = open("server.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd == -1) {
perror("open log file");
return -1;
}
然后在子进程中可以直接使用这个文件描述符进行日志记录。 2. 内存分配策略:
- 共享内存:对于一些需要在多个进程间共享的数据,如缓存数据等,使用共享内存。主进程创建共享内存段,并将其映射到自己的地址空间,然后在fork子进程时,子进程继承这些映射关系。这样子进程可以直接访问共享内存中的数据,避免了数据的重复拷贝。例如:
// 创建共享内存段
key_t key = ftok(".", 'a');
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return -1;
}
// 映射到自己的地址空间
void *shared_mem = shmat(shmid, NULL, 0);
if (shared_mem == (void *)-1) {
perror("shmat");
return -1;
}
- 内存池:在主进程中初始化内存池,子进程可以从内存池中获取内存,减少频繁的malloc和free操作。内存池的实现可以采用链表等数据结构,预先分配一定数量的内存块,子进程需要内存时从链表中取出,使用完毕后再放回链表。
进程间通信
- 使用高效的通信机制:
- 管道优化:如果使用管道进行进程间通信,对于双向通信的场景,可以采用一对命名管道(FIFO),而不是普通管道。命名管道可以在不同进程组之间使用,并且在进程启动时就可以创建好,避免了普通管道只能在父子进程间使用且需要在fork后立即创建的局限性。例如:
// 创建命名管道
if (mkfifo("fifo1", 0666) == -1) {
perror("mkfifo");
return -1;
}
if (mkfifo("fifo2", 0666) == -1) {
perror("mkfifo");
return -1;
}
- 信号量优化:在使用信号量进行进程同步时,尽量减少信号量的操作次数。例如,可以将多个操作合并成一个信号量操作,避免频繁的P、V操作。可以使用计数信号量来控制对共享资源的访问数量,当子进程需要访问共享资源时,先执行P操作,访问完毕后执行V操作。
- 减少通信开销:
- 减少不必要的通信:分析业务逻辑,确定哪些数据是真正需要在进程间传递的,避免不必要的数据传输。例如,对于一些只在本地进程内使用的数据,无需通过进程间通信传递。
- 批量数据传输:如果需要传递大量数据,采用批量传输的方式,而不是逐字节或逐小块传输。这样可以减少通信次数,提高效率。比如可以将多个小数据块组装成一个大数据包进行发送。