为什么pipe.write阻塞一个线程?

时间:2018-03-30 20:42:49

标签: ruby

以下是示例程序:

from = File.open('test.bin', 'rb')
to = IO.popen('cat', 'w+b')
i = 0

while buff = from.read(4096)
  to.write(buff) # blocks here
  i += 1
  print "#{i}, #{buff.size} bytes read                \r"
end

它读取二进制文件(您可以在Linux上使用fallocate -l 1MB test.bin创建它)并管道到cat命令。

然而,to.write(buff)来电挂起。以下是它在控制台中的外观:

[1] pry(main)> from = File.open('test.bin', 'rb')
#<File:test.bin>
-rw-rw-r-- 1 admin admin 1000000 Mar 30 13:38 test.bin
[2] pry(main)> to = IO.popen('cat', 'w+b')
#<IO:fd 14>
[3] pry(main)> i = 0
0
[4] pry(main)> 
[5] pry(main)> while buff = from.read(4096)
[5] pry(main)*   to.write(buff)  
[5] pry(main)*   i += 1  
[5] pry(main)*   print "#{i}, #{buff.size} bytes read                \r"  
[5] pry(main)* end  
33, 4096 bytes read                

因此它只写入135168个字节。但为什么呢?

另外,如果我使用不同的命令(我需要它用于带有一些参数的gpg),字节数是不同的(大约60 MB),但结果是相同的,它在完全相同的点处阻塞(这一点是不同的对于catgpg,无论您运行该程序多少次,每个程序都保持不变。)

环境:Ubuntu Linux,ruby 2.3.4p301

1 个答案:

答案 0 :(得分:2)

它阻塞的原因是因为IO.popen打开一个带有写和读文件句柄以及相关读写缓冲区的管道。

由于您根本不从io对象的读取句柄读取,因此读取缓冲区最终会变满(因为cat不断将其输入复制到其输出),并且读取缓冲区已满,OS阻塞管道的写入侧,直到再次在读取缓冲区中有空间。

您已经有效地创建了死锁情况。

解决方案是继续从读取端读取,以防止读取缓冲区阻塞,或者不使命令输出任何内容。

从读句柄读取可能很棘手,因为它本身就是一个阻塞调用。您需要设置非阻塞读取,或者您可以使用单独的线程来进行读取。

然而,最简单的解决方案是阻止cat输出任何内容,因此您永远不会创建死锁情况:

to = IO.popen('cat > /dev/null', 'w+b')

这可能是也可能不是你所追求的,但它应该给你一些关于如何继续的想法。