You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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缓冲区满后也会阻塞,因此需保持读写节奏匹配。

未来避坑方案

  1. 统一流对象:始终替换sys.stdin/sys.stdout为自定义的TextIOWrapper,而非创建独立流对象,确保Python所有I/O操作使用统一的编码和缓冲策略。
  2. 显式设置行缓冲:在Windows环境下,创建TextIOWrapper时显式指定line_buffering=True,确保行数据及时刷新,避免缓冲滞留。
  3. 同步读写节奏:Java端保持"写-读"的同步逻辑,避免单向写数据导致Python输出缓冲区溢出。
  4. 考虑异步读取:对于大规模数据,Java端可使用异步线程读取Python输出,防止因输出堆积导致Python阻塞。

内容的提问来源于stack exchange,提问作者Zyxl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:49:53