Java、子流程和未读输出流:什么时候死锁



当使用Runtime.exec()在java中创建子流程时,我知道我必须填充输入/排出输出流,以防止子流程阻塞。

有趣的是,Process的javadoc声明了更多:

...failure to promptly write the input stream or read the output stream of
the subprocess may cause the subprocess to block, and even deadlock.

我想知道,在这种情况下,子流程是否也会死锁

问题:
1.在什么条件下会发生死锁
2.为什么会陷入僵局
3.你能提供一个简短的示例程序来显示这种死锁吗
4.这种死锁是操作系统中的错误吗?

当父级在读取任何输出之前试图向其子级的输入流发送过多数据时,由于缓冲区大小有限,可能会发生死锁。

考虑一下这个代码:

final int LINES = 10;
// "tr" is a Unix command that translates characters;
// Here, for every line it reads, it will output a line with
// every 'a' character converted to 'A'.  But any command that outputs
// something for every line it reads (like 'cat') will work here
Process p = Runtime.getRuntime().exec("tr a A");
Writer out = new OutputStreamWriter(p.getOutputStream());
for (int i = 0; i < LINES; i++) {
    out.write("abcdefghijklmnopqrstuvwxyzn");
}
out.close();
// Read all the output from the process and write it to stdout
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
    System.out.println(line);
}

对于lines的小值,它将正常工作;在我们开始读取之前,tr的所有输出都可以放在操作系统缓冲区中。但是,对于较大的值(>10000应该足够了),操作系统缓冲区时会被填满;在tr命令中,对write的调用将被阻止,等待缓冲区被耗尽,而Java代码正在写入的缓冲区将被填满(因为tr被阻止,阻止它从输入中读取),进而阻止我们对out.write的调用,导致死锁,两个进程都在等待写入未被主动读取的完整缓冲区。

这种死锁不是操作系统中的错误,因为进程间通信的缓冲区大小有限是经过深思熟虑的设计决定。替代方案(缓冲区大小不受限制)有一些缺点:

  • 可能耗尽内核内存
  • 为了避免上述情况,如果缓冲区自动"溢出"到磁盘,可能会导致不可预测的性能,并可能填满磁盘
<小时>

顺便说一句,死锁也可能由于进程内缓冲区而发生。假设,为了解决上述死锁,我们将Java代码更改为先写一行,然后交替读取一行。然而,当Linux进程没有直接写入终端时,它们通常不会在每一行之后刷新。因此,tr可能读取一行,并将其写入其libc输出缓冲区,然后阻止等待写入下一行——而我们的Java代码将阻止等待tr输出一行。

死锁总是涉及至少2名参与者。因此,可能死锁的不仅仅是子流程,而是父流程和子流程的组合。

例如:

子进程想要读取一行,对其进行处理并打印结果。父进程认为它可以先读取子进程的输出,然后再向其提供输入

结果:死锁,两个进程都在等待另一个进程的输入。

因此,不,这不是操作系统中的错误,而是(父)进程中的逻辑错误。

最新更新