fork()后父子进程谁先运行无确定顺序,由内核CFS调度器决定;子进程退出后父进程不wait()会导致僵尸进程;子进程未exec时必须用_exit()而非exit()。

fork() 后父子进程谁先运行?
没有确定顺序。fork() 返回后,父子进程谁先被调度执行,完全由内核 CFS 调度器决定,不是“子进程一定等父进程 return 之后才跑”。很多新手写测试代码发现子进程总在父进程后面打印,误以为是顺序保证——那只是运气好或负载低下的巧合。
- 若需严格同步(比如父进程必须等子进程初始化完再发数据),必须显式调用
waitpid()或使用pipe()/signal()等 IPC 机制 - 在循环 fork 场景(如预分叉服务器)中,不加同步极易引发竞态,比如多个子进程同时尝试绑定同一端口而失败
-
fork()实际开销极小,靠写时复制(Copy-on-Write)延迟物理页拷贝;但若子进程立即大量写内存(如 deep copy 大结构体),反而比直接 malloc 更慢
execve() 失败的常见原因和调试方法
execve() 不创建新进程,只替换当前进程映像;它成功就永不返回,失败才返回 -1 并设置 errno。最常见的失败不是“找不到文件”,而是路径、权限或解释器问题。
- 路径错误:
execve("ls", ...)失败(ENOENT),必须用"./ls"或"/bin/ls";想自动查$PATH,改用execvpe() - 脚本无
#!:比如执行./deploy.sh时内核报ENOEXEC,说明该脚本第一行缺失#!/bin/bash或解释器路径不可达 - 权限不足:目标文件无可执行位(
chmod +x缺失),或位于 noexec 挂载分区(如某些 tmpfs) - 调试技巧:在 exec 前加
printf("about to exec: %s\n", argv[0]); fflush(stdout);,避免因缓冲区未刷导致日志丢失
子进程退出后,父进程不 wait() 会怎样?
子进程变成僵尸(Z 状态),ps 显示 STAT 列为 Z,PID 无法复用,长期积累会耗尽进程表(/proc/sys/kernel/pid_max 限制)。注意:僵尸本身不占内存/CPU,但它的 task_struct 和退出状态仍驻留内核。
iOS多线程编程对于iOS开发初学者来说,总是会觉得很难理解和掌握,现在通过几个实例来更加系统全面的理解iOS多线程编程,希望对大家有所帮助。 有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆如操作系统,一直运行直到你关机。 一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运行,
- 临时补救:父进程调用
waitpid(-1, &status, WNOHANG)非阻塞回收;或注册SIGCHLD信号处理器,在其中循环waitpid() - 彻底省事:父进程启动前设
signal(SIGCHLD, SIG_IGN),Linux 2.6+ 内核会自动清理(但注意:某些旧系统或容器环境可能不生效) - 双 fork 技巧:子进程再 fork 一个孙进程后立即
_exit(),孙进程被 init 收养,自然由 init 负责 wait —— 常用于守护进程脱离终端
exit() 和 _exit() 在 fork 后到底该用哪个?
子进程若已调用 execve(),用 exit() 或 _exit() 都行;但若没 exec(比如只做计算就退出),**必须用 _exit()**,否则可能重复刷新父进程的 stdio 缓冲区,导致日志错乱或文件写入异常。
- 根本原因:fork 后父子进程共享底层文件描述符(fd),但各自有独立的
FILE*结构体;exit()会 flush 所有打开的FILE*,而父子 fd 指向同一文件,造成重复写 - 典型现象:父进程 printf("done\n") 后 fork 子进程,子进程直接
exit(0),结果终端看到两行 "done" - 安全习惯:fork 后的子进程,只要不 exec,退出一律用
_exit();父进程则可用exit()(需确保不依赖子进程 flush 行为)
_exit() 的使用时机和 SIGCHLD 信号处理的异步安全性——比如在信号处理器里调用 printf() 或 malloc() 会引发未定义行为,必须只用 async-signal-safe 函数(如 write()、_exit())。









