Java与Python进程用readline()通信时冻结问题的技术问询
问题背景
我有一个需要处理大量输入字符串的Java程序,逻辑是逐个将字符串传递给Python脚本进程,获取输出后再处理下一个字符串。运行数小时后,Java会因等待Python输出而冻结。
为调试我编写了简化版程序:使用短字符串,Java端无缓冲,Python脚本不修改数据,但冻结位置变为Java向Python刷数据、Python向Java刷结果的阶段。每次运行能处理的任务数略有差异,字符串越长,可处理数量越少。
相关代码
Java端代码
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; public class Main { public static void main(String[] args) throws IOException, InterruptedException { Process process = start("python", "test.py"); for (int i = 0; i < 1000; i++) { System.out.println(i); processText("test string test string test string test string ", process); } process.getOutputStream().close(); boolean finished = process.waitFor(10, SECONDS); if (!finished) { process.destroyForcibly(); } } public static Process start(String... command) throws IOException { ProcessBuilder processBuilder = new ProcessBuilder(command); processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT); return processBuilder.start(); } public static String processText(String text, Process process) throws IOException { byte[] bytes = (text + "\n").getBytes(UTF_8); OutputStream outputStream = process.getOutputStream(); System.out.println("Writing..."); outputStream.write(bytes); System.out.println("Done!"); outputStream.flush(); System.out.println("Reading..."); String result = readLn(process); System.out.println("Got it!"); return result; } public static String readLn(Process process) throws IOException { InputStream inputStream = process.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte newlineByte = "\n".getBytes(UTF_8)[0]; byte lastByte = -1; while (lastByte != newlineByte) { lastByte = (byte) inputStream.read(); byteArrayOutputStream.write(lastByte); } return byteArrayOutputStream.toString(UTF_8); } }
原始Python脚本
import sys import io in_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') out_stream = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') def output(s): out_stream.write(s) text = in_stream.readline() while text != "": print("Outputting result", file=sys.stderr) output(text) print("Output done!", file=sys.stderr) output("\n") print("Flushing", file=sys.stderr) out_stream.flush() print("Flushed", file=sys.stderr) text = in_stream.readline()
运行现象
Java输出
0 Writing... Done! Reading... Got it! 1 Writing... Done! Reading... Got it! ... 379 Writing... Done! Reading... Got it! 380 Writing... Done! [Freezes here]
Python(stderr)输出
Outputting result Output done! Flushing Flushed ... Outputting result Output done! Flushing Flushed Outputting result Output done! Flushing [Freezes here]
强制终止后的报错信息
强制终止Java后,Python的stderr新增以下输出:
Flushed Outputting result Output done! Flushing Traceback (most recent call last): File "...\test.py", line 17, in <module> out_stream.flush() OSError: [Errno 22] Invalid argument Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='cp1252'> OSError: [Errno 22] Invalid argument
临时修复后的Python脚本
将sys.stdin替换为UTF-8编码的TextIOWrapper,并使用input()读取数据后,程序可以完成全部1000个任务:
import sys import io sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') out_stream = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') def output(str): out_stream.write(str) def read_text(): try: return input() except EOFError: return "" text = read_text() while text != "": print("Outputting result", file=sys.stderr) output(text) print("Output done!", file=sys.stderr) output("\n") print("Flushing", file=sys.stderr) out_stream.flush() print("Flushed", file=sys.stderr) text = read_text()
问题分析与解决方案
冻结原因
核心问题出在Windows管道缓冲机制与Python自定义流的冲突:
- 原始脚本中仅创建了独立的
in_stream但未替换sys.stdin,in_stream.readline()的缓冲策略与系统默认stdin的缓冲逻辑不兼容,导致管道中的输入数据被滞留。 - 当数据滞留后,Python等待读取完整的输入行,Java等待Python输出结果,双方互相阻塞形成死锁。
修复原理
替换sys.stdin为自定义的UTF-8编码TextIOWrapper后,input()会直接使用这个统一的流对象。Python的input()内部会正确处理行缓冲和管道数据的读取逻辑,避免了数据滞留,打破了死锁循环。
编码报错说明
报错中的cp1252是Python默认的sys.stdout编码,但你创建的out_stream已经绑定了sys.stdout.buffer并指定UTF-8,实际数据传输是UTF-8编码。该报错是Java进程终止后管道失效,Python刷新输出时触发的无效参数错误,和编码设置无关。
关于out_stream的风险
只要确保每次输出后调用flush(),且Java端同步读取输出,out_stream本身不会导致冻结。但如果Java端未及时读取,Python的stdout缓冲区满后也会阻塞,因此需保持读写节奏匹配。
未来避坑方案
- 统一流对象:始终替换
sys.stdin/sys.stdout为自定义的TextIOWrapper,而非创建独立流对象,确保Python所有I/O操作使用统一的编码和缓冲策略。 - 显式设置行缓冲:在Windows环境下,创建
TextIOWrapper时显式指定line_buffering=True,确保行数据及时刷新,避免缓冲滞留。 - 同步读写节奏:Java端保持"写-读"的同步逻辑,避免单向写数据导致Python输出缓冲区溢出。
- 考虑异步读取:对于大规模数据,Java端可使用异步线程读取Python输出,防止因输出堆积导致Python阻塞。
内容的提问来源于stack exchange,提问作者Zyxl

