Java 中通过 Runtime.exec 创建子进程时,父子进程管道通信问题

2022-05-26 10:09:58 +08:00
 linuxsteam

小弟最近在研究父子进程中如何用管道进行通信,但是遇到一个情况,目前无法理解现有的答案。

代码复现

shell 脚本

#!/bin/bash

for((i=0; i<10913; i++));do
    # 输出到 stdin
    echo "input"
    # 输出到 stderr
    echo "error" 1>&2
done

java

public static Object executeCommand(String command) throws Exception
    {
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        Process process = processBuilder.start();
        readStreamInfo(process.getInputStream(), process.getErrorStream());
        int exit = process.waitFor();
        process.destroy();
        if (exit == 0)
        {
            System.out.println("子进程正常完成");
        }
        else
        {
            System.out.println("子进程异常结束");
        }
        return null;
    }

    private static void readStreamInfo(InputStream... inputStreams){
        try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStreams[0]),8192))
        {
            String line;
            int i = 0;
            while (true)
            {
                String s = br.readLine();
                if (s != null)
                {
                    System.out.println(++i + " " + s);
                }
                else
                {
                    break;
                }
            }
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        finally
        {
            try
            {
                inputStreams[0].close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }


        try (BufferedReader bufferedInput = new BufferedReader(new InputStreamReader(inputStreams[1])))
        {
            String line;
            int i = 0;
            while ((line = bufferedInput.readLine()) != null)
            {
                System.out.println(++i + " " + line);
            }
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        finally
        {
            try
            {
                inputStreams[1].close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

实测断点会卡在 String s = br.readLine();迟迟没有收到返回值。

shell 中 for 循环减少一次错误流输出上述代码就不会阻塞。

所以我参考网上搜索结果和查阅书籍下了个结论:

以上问题是缓冲区满了导致的

但是还是有几个问题不能理解,希望有研究过的大佬可以帮帮小弟。

问题

2560 次点击
所在节点    Java
34 条回复
linuxsteam
2022-05-26 19:26:01 +08:00
@Bingchunmoli 解决方案我了解的。
除了这个方法 还可以把标准错误流 重定向到一个流中,这样单线程也可以
linuxsteam
2022-05-26 19:32:49 +08:00
@zmal 跟 read 没关系 他们底层都是调用 FileInputStream 的 private native int readBytes(byte b[], int off, int len)
linuxsteam
2022-05-26 19:34:49 +08:00
@senninha ```shell
#!/bin/bash

# 输出到 stdin
echo "input"
for((i=0; i<10913; i++));do
# 输出到 stderr
echo "error" 1>&2
done
```

#19 stdout 什么时候才算结束?
这个例子就一个 stdout ,
为啥还会卡在循环中?
senninha
2022-05-26 19:54:49 +08:00
@linuxsteam exec 1>&-
关掉 stdout 再试试看

```
echo "input"
# close stdout
exec 1>&-
for((i=0; i<10913; i++));do
# 输出到 stderr
echo "error" 1>&2
done
```
haah
2022-05-26 19:56:15 +08:00
参考 Apache commons-exec ,你都不嫌复杂么?
senninha
2022-05-26 19:56:30 +08:00
@linuxsteam stdout 手动关闭,或者在进程终止的时候,父进程才会收到 EOF
linuxsteam
2022-05-26 19:56:52 +08:00
@senninha
#20
https://pastebin.com/0cVtyGCr
老哥能看看这个结果吗 这个 gdb 的资料真是太少了。。。
linuxsteam
2022-05-26 20:06:53 +08:00
@haah 我想研究研究,这个源码也有在看。
@senninha #24 可以了😭
#26 谢谢大佬,进程终止才会发 eof 这个和 JDK 的 API 的 readBytes()注释对上了 😭
小弟还有两个问题
1. 请问 子进程和父进程通信时候,stdout stdin stderr 他们要开三个管道吗?还是一个管道就可以?
2.
```shell
echo "input"
for((i=0; i<10912; i++));do
# 输出到 stderr
echo "error" 1>&2
done
```shell
为啥少输出一次就不会阻塞了?
linuxsteam
2022-05-26 20:07:49 +08:00
@senninha #27 楼请忽略。。。 这个看不看已经没有必要了😂
senninha
2022-05-26 20:09:12 +08:00
@linuxsteam 这个栈就是阻塞在 write 标准输出上了啊,你看一下 24L 说的这种方式,shell 关掉 stdout 后,Java 那边就结束对 stdout 的读取,可以读取 stderr 的输出,shell 应该就不会 hang 住了。
linuxsteam
2022-05-26 20:14:47 +08:00
@senninha 是的,24 楼那个我已经试过了。可以通过。。。诶 我看了两本操作系统的书 进程通信中管道章节。都没有找到大佬您说的这几个关键点😭
linuxsteam
2022-05-26 20:17:38 +08:00
@linuxsteam 为啥少输出一次就不会阻塞了 的问题也不用回复了
我明白了: 因为 err 一直在写,进程没有结束 所以就阻塞了
msg7086
2022-05-27 03:41:58 +08:00
Stdout 和 stderr 需要同时读取,否则就会因为 err 写爆了而阻塞。把读 err 的放进线程里并行跑就好了。
msg7086
2022-05-27 03:44:27 +08:00
阻塞就相当于:如果没人读取(清空) stderr ,那就让程序无限等待,直到有人读取(清空)了 stderr 为止。
你 Java 代码没有读 stderr ,那进程就会永久卡住。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/855368

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX