是否可以在Ruby中使用IO.pipe进行线程间通信?

时间:2017-12-30 18:00:42

标签: ruby multithreading ipc ruby-thread

在文档中的Ruby IO.pipe示例中,单个消息将通过这些进程传递。

我想做类似的事情,有两点不同:

  1. 使用线程代替进程
  2. 使用管道进行持续消息传递,而不是使用一次性消息
  3. 这是显而易见但无效的代码:

    rd, wr = IO.pipe
    
    reader_thread = Thread.new(rd) do |rd|
      data_received = rd.read
      puts "Read: #{data_received.inspect}"
    end
    
    write_thread = Thread.new(wr) do |wr|
      wr.write "Message"
      wr.flush
    end
    
    write_thread.join
    reader_thread.join
    

    会导致reader_thread挂起rd.read

    我可以使用IO#read_nonblock

    使其有效
    reader_thread = Thread.new(rd) do |rd|
      data_received = \
        begin
          rd.read_nonblock(100)
        rescue IO::WaitReadable, IO::EAGAINWaitReadable
          IO.select([rd])
          retry
        end
    
      puts "Read: #{data_received.inspect}"
    end
    

    这是正确的模式吗?或者使用IO.pipe错误的工具进行线程消息传递?

2 个答案:

答案 0 :(得分:0)

您还可以使用Queue在多个线程之间安全地交换信息:

reset

set terminal pngcairo size 750,9.0 font ",10"
set output "parametric.png"

unset tics
unset border
set view equal xyz
set view ,,2
set view 100,30
set xyplane 0
set hidden3d

set parametric
set urange [0:2*pi]
set vrange [0:pi/2]

set style line 1 lc "red" lw 2

f(u,v) = cos(u)*cos(v)
g(u,v) = sin(u)*cos(v)
h(v)   = sin(v)

set table $hemisphere
    splot f(u,v), g(u,v), h(v)
unset table

set multiplot layout 4,2 columnsfirst
    splot f(u,v), g(u,v), h(v) w l ls -1 t "Using functions (with ls -1 )"
    splot f(u,v), g(u,v), h(v) w l lt  3 t "Using functions (with lt 3)"
    splot f(u,v), g(u,v), h(v) w l ls  1 t "Using functions (with custom ls 1)"
    splot f(u,v), g(u,v), h(v) w l lc "red" t "Using functions (with lc 'red')"
    splot $hemisphere w l ls -1 t "Using datablock (with ls -1)"
    splot $hemisphere w l lt  3 t "Using datablock (with lt 3)"
    splot $hemisphere w l ls  1 t "Using datablock (with custom ls 1)"
    splot $hemisphere w l lc "red" t "Using datablock (with lc 'red')"
unset multiplot 

答案 1 :(得分:0)

您的读取器线程挂起,因为没有参数,IO.read 将读取 -- 并阻塞 -- 直到遇到 EOF。 (如果你传递一个 length,它会一直读取,直到它读取那么多字节,或者一个 EOF,以先发生者为准,所以它仍然会阻塞,直到它至少得到那么多输入。)这在 中有详细解释IO.pipe docs

如果您在 wd.close 之前调用 reader_thread.joinread 将获得该 EOF,您将获得输出 - 当 read 解除阻塞时,一次性完成。

在现实场景中,您可能不只是想读取一次,您可能想循环直到 rd 遇到 EOF,并在此过程中对数据进行处理。最简单的事情就是一次读取一个字节,使用 read(1)。 (为了简单起见,我省略了单独的编写器线程——你也应该这样做,除非你真的需要三个单独的指令流;通常你会想要一个后台读取器线程或一个后台写入器线程,主线程处理另一端——但行为基本相同。

text = <<~TEXT.strip
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
  sed do eiusmod tempor incididunt ut labore et dolore magna 
  aliqua.
TEXT

read_io, write_io = IO.pipe

reader_thread = Thread.new(read_io) do |io|
  puts('Reading:')
  while (c = io.read(1)) # block till we read one byte
    $stdout.write(c)
  end
  puts('...Done.')
end

# Write 50 chars/second, so we can see them get read one at a time
text.chars.each { |c| write_io.write(c); sleep(0.02) } 

reader_thread.join

# => Reading:
#    Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
#    sed do eiusmod tempor incididunt ut labore et dolore magna 
#    aliqua.

不过,这仍然挂起,因为 IO.read(1) 仍在等待那个 EOF,所以同样,您需要关闭 write_io

此外,逐字节读取通常效率不高。实际上,您可能需要 8K 缓冲区或 even larger,具体取决于您的用例。

reader_thread = Thread.new(read_io) do |io|
  puts('Reading:')
  while (c = io.read(8192))
    $stdout.write(c)
  end
  puts('...Done.')
end

# We're writing 50 chars/second, but we won't see them print out
# till `read_io` has read 8192 bytes, or hit an EOF
text.chars.each { |c| write_io.write(c); sleep(0.02) }

write_io.close      # we have to close `write_io` *sometime* --
reader_thread.join  # -- or this will hang.

# => Reading:
#    Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
#    sed do eiusmod tempor incididunt ut labore et dolore magna 
#    aliqua....Done.