Ruby Fork API
条件判断式
puts "pid: #{Process.pid}"
if son_pid = fork
# 仅父进程执行: fork 在父进程中返回子进程的 pid
puts "This is father, son_pid: #{son_pid}, pid: #{Process.pid}"
else
# 仅子进程执行: fork 在子进程中返回 nil
puts "This is son, son_pid: #{son_pid}, pid: #{Process.pid}"
end
# 之后的部分父子进程都会分别执行到
puts 'end'
OUTPUT:
pid: 18670
This is father, son_pid: 18671, pid: 18670
end
This is son, son_pid: , pid: 18671
end
代码块式
puts "pid: #{Process.pid}"
pid = fork do
# block 仅在子进程执行, 之后便退出
puts "This is son, pid: #{Process.pid}"
end
# 后续仅在父进程执行
puts "This is father, pid: #{Process.pid}"
OUTPUT:
pid: 20002
This is father, pid: 20002
This is son, pid: 20003
注意: fork
不能用在 windows 下, 如果要跨平台, 应该使用 spawn
, spawn
也支持能丰富的参数.
僵尸进程和子进程清理
进程退出后, 虽然会释放内存/文件描述符等资源, 但内核仍会保留一些元信息供其父进程查看. 这些元信息含有该进程的pid, 如果不清理这些元信息, 该pid就不能被重用.
如果父进程先退出, 此时还在执行的子进程称作孤儿进程, 没有被清理的子进程(可能比父进程先结束, 也可能后结束)将由 init 进程接管并负责清理.
如果父进程持续运行, 子进程没有被清理, 那么这个结束的子进程就成为一个僵尸进程, 此时应该对结束的子进程进行清理:
- 要么调用
Process.wait(son_pid)
等 wait 系列方法(wait/wait2/waitpid/waitpid2/waitall); - 要么调用
Process.detach(son_pid)
来分离子进程.
wait 返回子进程pid, wait2 返回 [son_pid, status]
.
wait 阻塞主进程, 直到任意一个子进程退出;
wait(son_pid) 阻塞主进程, 直到 son_pid 对应的子进程退出.
detach 的本质上是新开一个线程来 Process.wait(son_pid)
, 所以它能实现 wait 的回收功能, 且不会阻塞主进程.
已经 wait 过的 son_pid 不能重复 wait, 否则报 ` No child processes (Errno::ECHILD)` .
父进程通过信号完成对子进程的监控
Ruby 中使用 trap
来重新定义收到信号的行为.
常用的1-31号的信号是不可靠信号, 不支持排队.
wait(son_pid)
又是一个同步的阻塞调用, 如果多个子进程同时发信号, 就会造成叠加信号丢失的现象.
wait(son_pid, Process::WNOHANG)
可以解决这个问题, 它以非阻塞的方式使用 wait:
- 如果子进程结束了, 返回子进程pid;
- 如果子进程还在执行, 返回 nil;
- 如果没有子进程了, 抛出
Errno::ECHILD
.
下面的例子中, 主进程负责任务分配和子进程管理, 各个子进程负责做具体的事情:
puts 'this is mater process'
def calc(num)
sleep 0.5 * num
num ** 2
end
WORKER_COUNT = 5
dead_process_count = 0
# 产生5个具体做事的worker, 进行耗时计算
WORKER_COUNT.times do |i|
fork do
puts "calc(#{i}): #{calc(i)}"
end
end
# 当子进程退出之后, 主进程会收到 CHLD 的信号
trap(:CHLD) do
# WNOHANG(Wait No Hang) 不会阻塞的 wait
begin
while son_pid = Process.wait(-1, Process::WNOHANG)
puts "Received CHLD From PID: #{son_pid}"
dead_process_count += 1
end
rescue Errno::ECHILD
# 如果再没有子进程了, wait 将抛出该异常
puts "dead_process_count: #{dead_process_count}"
end
end
# CTRL-C 正常退出
trap(:INT) do
at_exit { puts 'BYE!' }
exit
end
# master 不死
sleep