条件判断式

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