当使用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名参与者。因此,可能死锁的不仅仅是子流程,而是父流程和子流程的组合。
例如:
子进程想要读取一行,对其进行处理并打印结果。父进程认为它可以先读取子进程的输出,然后再向其提供输入
结果:死锁,两个进程都在等待另一个进程的输入。
因此,不,这不是操作系统中的错误,而是(父)进程中的逻辑错误。