上一讲我们详细分析了select/poll的文件描述符溢出问题,强调了FD_SETSIZE限制、fd值溢出风险、及时回收fd、API返回值检查,以及高并发场景应优先使用epoll/kqueue等高效机制。
1. 主题原理与细节逐步讲解
在C语言开发中,多进程(通常由fork()创建)用于并行处理任务、提升系统吞吐量。与多线程不同,多进程间地址空间隔离,但某些资源(如文件描述符、信号量、共享内存)是可以共享或竞争的。
常见可共享/竞争资源:
- 文件描述符(fd)
- 信号
- 内存映射区(mmap, shm等)
- 管道、套接字
- 临时文件/锁文件
- 系统IPC对象(信号量、消息队列、共享内存等)
多进程资源竞争问题本质:
- 父/子进程在继承或访问共享资源时,若没有适当同步与管理,会引发死锁、数据损坏、资源泄漏等问题。
2. 典型陷阱/缺陷及成因剖析
2.1 文件描述符未关闭导致数据混乱或泄漏
成因:
- fork后父子进程均持有同样的fd,对同一个fd读写,可能导致数据错乱或多次关闭同一fd(竞态)。
- 比如父/子均持有写端,关闭顺序混乱,管道不会及时关闭或数据丢失。
2.2 临时文件、锁文件竞争
成因:
- 多进程同时创建、写入或删除同一临时文件,缺乏唯一性和同步,易引发数据覆盖、删除时机错误。
2.3 信号误处理与竞争
成因:
- 信号由内核分发到进程,若父/子注册了同样的信号处理函数,可能导致信号响应混乱或丢失。
2.4 共享内存区同步失败
成因:
- 多进程通过mmap/shm共享内存,但未正确同步(如缺乏互斥锁),导致数据竞争、写入冲突。
2.5 同步原语(如信号量)初始化不当
成因:
- 父进程创建同步原语,fork后子进程未正确继承或初始化,导致同步机制失效。
2.6 资源回收漏掉
成因:
- 父/子进程未能及时关闭/删除IPC对象,导致系统资源泄漏(如sem, shm, pipes未unlink)。
3. 规避方法与最佳设计实践
- fork后,父子进程应根据业务主动关闭或重定向不需要的文件描述符,避免资源竞争。
- 临时文件/锁文件应带唯一标识(如PID),并用原子操作创建(O_EXCL | O_CREAT),防止冲突。
- 信号处理函数在fork后应根据实际需求重新注册,避免父子进程混淆信号响应。
- 共享内存区需加互斥/信号量同步,保证写入原子性,防止数据竞争。
- 所有IPC对象(信号量、共享内存等)创建后要有明确的回收流程(如sem_unlink, shm_unlink),防止系统资源泄漏。
- 避免多进程同时写同一文件,必要时加文件锁(flock, fcntl)。
- 多进程间通信建议用专属管道、socketpair,避免fd混用。
- fork后只保留必要资源,其他一律关闭,尤其在daemon化、重定向场景下。
4. 错误代码与优化代码对比
错误示例1:父子进程同时读写同一fd
int fd = open("data.txt", O_RDWR);
pid_t pid = fork();
if (pid == 0) {
// 子进程
write(fd, "child data\n", 11);
close(fd);
} else {
// 父进程
write(fd, "parent data\n", 12);
close(fd);
}
问题:父子进程写入顺序不可控,数据可能交错或丢失。
优化后:fork后只保留一方fd,另一方主动关闭
int fd = open("data.txt", O_RDWR);
pid_t pid = fork();
if (pid == 0) {
// 子进程
close(fd); // 或只读/只写时只保留需要的fd
// 重新打开自己的fd或通过IPC通信
} else {
// 父进程
// 父进程继续使用fd
}
错误示例2:临时文件竞争
char tmpfile[] = "/tmp/mytmp";
int fd = open(tmpfile, O_CREAT | O_RDWR, 0600); // 多进程间冲突
优化后:带唯一标识,原子创建
char tmpfile[64];
snprintf(tmpfile, sizeof(tmpfile), "/tmp/mytmp_%d", getpid());
int fd = open(tmpfile, O_CREAT | O_EXCL | O_RDWR, 0600);
错误示例3:共享内存未同步
void *mem = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 多进程直接写mem[0] = ...; // 无保护
优化后:加信号量或互斥锁同步
// 父进程初始化POSIX信号量/互斥锁
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
// 多进程访问时:
sem_wait(sem);
mem[0] = ...;
sem_post(sem);
错误示例4:IPC对象未回收
// 创建共享内存
shm_open("/myshm", O_CREAT | O_RDWR, 0666);
// 用完未 shm_unlink("/myshm");
优化后:使用完后主动回收
shm_unlink("/myshm");
sem_unlink("/mysem");
5. 底层原理补充
- fork后,子进程复制父进程文件描述符表,fd指向同一内核对象,fd引用计数增加。
- mmap或shm创建的共享内存区,物理上允许多进程读写,需用户代码保证同步。
- POSIX IPC对象(sem, shm)在系统级有唯一标识,需主动回收,否则资源长期占用。
- 信号由内核分发,fork后父子进程共享信号掩码,建议根据需求重新配置。
6. 图示:多进程资源共享与竞争

7. 总结与实际建议
- 多进程资源共享需严格同步与管理,否则易发死锁、数据错乱、资源泄漏。
- fork后主动关闭/重定向不需要的fd,IPC资源要带唯一标识并原子创建。
- 所有共享区都要加互斥机制,严防竞争。
- 信号、锁、临时文件等资源管理需有全流程,防止遗留垃圾。
- 多进程通信建议专用IPC通道,避免混用fd。
- 工程代码要定期审查资源回收和同步机制,防止线上故障。
核心建议:多进程并发场景下,资源同步与竞争管理是稳定运行的关键,需全流程设计与严格编码规范。
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top
1414

被折叠的 条评论
为什么被折叠?



