为什么在两个分支中关闭两个 IO 管道的顺序很重要?

Why does the order of closing two IO pipes in two forks matter?

提问人:maikovich 提问时间:10/28/2023 最后编辑:maikovich 更新时间:10/28/2023 访问量:40

问:

在 Ruby 中运行以下代码会无限期挂起。这似乎是因为在第一个子进程中没有被父进程解锁。Process.wait p1r1.eof?w1.close

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

我的困惑在于为什么这不起作用,以及为什么以下内容(关闭编写器并等待进程 1 和进程 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

但是,一旦我将第二个片段的逻辑放在一个类中,它就不再起作用了:

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)

这是怎么回事?

更深入地挖掘,并利用父进程迄今为止创建的内存中的所有文件描述符都将在分叉进程中可用的知识,我发现我可以通过将所有打开的 IO 读取器和写入器存储在一个类变量中来使其工作,并使用 关闭 fork 中所有不相关的读取器和写入器, 这样:@@opened_io@@opened_io.reject { |io| io.closed? || io == @r }.each(&:close)

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)

为什么这如此重要?

Ruby 多线程 IO 子进程 fork

评论


答: 暂无答案