Ruby中两个fork进程关闭IO管道的顺序为何影响程序运行?
Ruby管道与Fork进程阻塞的原因解析
Unix系统中,管道的读端调用
eof?会阻塞,直到该管道的所有写入端文件描述符被完全关闭——只要有一个写入端处于打开状态,读端就会认为可能还有数据过来,不会返回EOF。而Ruby的
fork操作会复制父进程的所有打开文件描述符到子进程中,这些副本和父进程的描述符指向同一个内核文件对象,只有当所有副本都关闭时,内核才会标记管道写入端为关闭状态。
1. 第一段代码挂起的原因
r1, w1 = IO.pipe p1 = fork { w1.close; r1.eof? } r1.close r2, w2 = IO.pipe p2 = fork { w2.close; r2.eof? } r2.close sleep 1 w1.close puts "Wait for p1"; Process.wait p1 w2.close puts "Wait for p2"; Process.wait p2
- 创建
p2子进程时,父进程仍持有w1的打开状态,因此p2会继承w1的文件描述符副本。 - 父进程关闭
w1后,p1管道的写入端还有p2子进程里的w1副本处于打开状态,导致p1的r1.eof?一直阻塞。 - 父进程卡在
Process.wait p1,而p2持续运行并持有w1,形成死锁。
2. 调换顺序后正常执行的原因
r1, w1 = IO.pipe p1 = fork { w1.close; r1.eof? } r1.close r2, w2 = IO.pipe p2 = fork { w2.close; r2.eof? } r2.close sleep 1 w2.close puts "Wait for p2"; Process.wait p2 w1.close puts "Wait for p1"; Process.wait p1
- 父进程先关闭
w2并等待p2:此时p2管道的所有写入端(父进程的w2和p2自身的w2)都已关闭,p2的r2.eof?返回true,进程正常退出。 p2退出后,它继承的w1副本会被系统自动回收关闭,此时p1管道的所有写入端(父进程的w1已关闭,p2的w1副本也已回收)都关闭,p1的r1.eof?返回true,父进程Process.wait p1顺利完成。
3. 类封装后异常的原因
class Client def initialize @r, @w = IO.pipe end def start @pid = fork { @w.close; @r.eof? } @r.close end def stop @w.close puts "Wait for #@pid"; Process.wait(@pid) end end clients = 2.times.map { Client.new } clients.each(&:start) sleep 1 clients.reverse.each(&:stop)
- 每个
Client实例创建的@w会被所有后续fork出的子进程继承:第一个Client的子进程持有第二个Client的@w副本,第二个Client的子进程持有第一个Client的@w副本。 - 调用第一个
Client的stop关闭@w时,第二个Client的子进程仍持有该@w副本,导致第一个Client的子进程中@r对应的管道写入端未完全关闭,@r.eof?阻塞,父进程Process.wait挂起。
4. 修复代码的原理
class Client @@opened_io = [] def initialize @r, @w = IO.pipe @@opened_io.concat([@r, @w]) end def start @pid = fork do @@opened_io.reject { |io| io.closed? || io == @r }.each(&:close) @r.eof? end @r.close end def stop @w.close puts "Wait for #@pid"; Process.wait(@pid) end end clients = 2.times.map { Client.new } clients.each(&:start) sleep 1 clients.each(&:stop)
- 在fork出的子进程中,主动关闭所有无关的IO描述符(仅保留当前子进程需要的
@r),避免子进程持有其他管道的写入端副本。 - 父进程调用
stop关闭对应@w后,该管道的所有写入端(父进程的@w已关闭,子进程未持有其他副本)都被关闭,子进程的@r.eof?返回true,进程正常退出,父进程Process.wait顺利完成。
内容的提问来源于stack exchange,提问作者maikovich
相关产品推荐
相关产品推荐

