A non-blocking read on a subprocess.PIPE in Python
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Reading from subprocess.PIPE is blocking by default, which becomes a problem when your main program needs to stay responsive while a child process is still running. The safest non-blocking pattern depends on your environment: threads are the most portable option, while selectors or asyncio are good fits when the rest of the program is already event-driven.
Why Pipe Reads Block
If you call read, readline, or iterate over a pipe directly, Python waits until data is available or the stream closes.
That readline() call blocks until a full line arrives. If the child is slow or writing partial output, your program waits too.
The Most Portable Pattern: Thread Plus Queue
A background reader thread is often the simplest cross-platform answer. The blocking read happens in the thread, while the main program polls a queue without freezing.
This pattern is popular because it works on Windows and Unix-like systems without relying on pipe readiness behavior that varies by platform.
selectors for POSIX-Style Readiness
If your program runs on Unix-like systems and you want readiness-based polling, selectors can be a good fit.
This is clean on POSIX systems, but it is less portable for subprocess pipes on Windows than the thread approach.
asyncio for Async Programs
If the surrounding application already uses asyncio, the async subprocess APIs are usually the best match.
This still waits for child output, but it does not block the entire event loop the way a normal pipe read would.
Buffering Is Part of the Problem Too
Sometimes the parent code is fine and the child process is what is buffering output. If you expect line-by-line updates, the child must flush appropriately.
For Python child processes, the -u flag helps:
Without line buffering or unbuffered output, the parent may appear to "miss" data even though the child simply has not flushed it yet.
Avoid Deadlocks from Unread Streams
If the child writes enough data to stdout or stderr and the parent never drains it, the child can block because the pipe buffer fills up. That is why it is important either to consume both streams or to redirect one into the other.
If you only need all output after the process exits, subprocess.communicate() is often simpler than building a streaming non-blocking reader at all.
Common Pitfalls
Trying to make the parent non-blocking while the child still buffers heavily is a common reason streaming output appears inconsistent.
Using readiness-based APIs and assuming subprocess pipe behavior is identical on Unix and Windows creates portability bugs.
Reading only stdout and ignoring stderr can still deadlock the child if the error stream fills its buffer.
Building a complex streaming solution when communicate() would have solved the actual use case is unnecessary complexity.
Treating one blocking readline() call as harmless can freeze the rest of the program if the child stalls.
Summary
- Reads from
subprocess.PIPEblock by default. - A reader thread plus
queue.Queueis the most portable non-blocking pattern. - '
selectorsis useful on Unix-like systems for readiness-based reading.' - '
asynciois the best fit when the rest of the application is already asynchronous.' - Always account for child-process buffering and unread
stderrwhen diagnosing hangs.

