为何pipe.write会阻塞线程?附示例程序及问题复现
pipe.write block the thread in this Ruby code? Let's break down your problem step by step, including why the block happens and how to fix it:
The Problem Context
You're using this Ruby code to read a binary file and pipe its content to the cat command, but the to.write(buff) call hangs indefinitely:
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
For testing, you create the input file with: fallocate -l 1MB test.bin
Why the Block Occurs
This is a classic case of pipe buffer deadlock on Unix/Linux systems:
- Pipes have a fixed-size buffer (usually ~64KB, depending on the OS). When you write to the pipe, the system will block your write call if the buffer is full, until the reading end consumes some data to free up space.
- The
catprocess reads from the pipe (its stdin) and writes the content to its stdout. But in your code, you opened the pipe inw+bmode but never read fromto(the pipe's stdout side). This meanscat's stdout has nowhere to go—eventually,catwill block on writing to its stdout, which in turn means it stops reading from the pipe. - With
catnot consuming data from the pipe, your Ruby process'sto.write(buff)call will hang once the pipe buffer is completely full.
Fixes You Can Use
There are a few straightforward solutions to break this deadlock:
1. Spawn a Thread to Consume the Pipe's Output
Create a separate thread to read from cat's output, so cat can keep processing input and freeing up the pipe buffer:
from = File.open('test.bin', 'rb') to = IO.popen('cat', 'w+b') # Thread to consume cat's output and prevent it from blocking Thread.new do while output = to.read(4096) # You can print the output here, or discard it if you don't need it # puts output end end i = 0 while buff = from.read(4096) to.write(buff) i += 1 print "#{i}, #{buff.size} bytes read \r" end to.close # Close the write end so cat knows there's no more data
2. Redirect cat's Output to /dev/null
If you don't care about cat's output at all, redirect its stdout to /dev/null—this lets cat run without blocking on output:
from = File.open('test.bin', 'rb') # Redirect cat's stdout to /dev/null to avoid blocking to = IO.popen('cat > /dev/null', 'w') i = 0 while buff = from.read(4096) to.write(buff) i += 1 print "#{i}, #{buff.size} bytes read \r" end to.close
3. Use IO.copy_stream for Simplified Data Copying
Ruby's built-in IO.copy_stream handles buffer management and pipe mechanics under the hood, making your code shorter and avoiding the blocking issue entirely:
from = File.open('test.bin', 'rb') to = IO.popen('cat > /dev/null', 'w') IO.copy_stream(from, to) from.close to.close
Summary
The core issue is mutual blocking: your write operation fills the pipe buffer, and cat blocks on its own output because you're not consuming it. By ensuring cat's output is handled (either read in a thread or discarded), you break the deadlock and let the data flow through the pipe properly.
内容的提问来源于stack exchange,提问作者Roman Pushkin




