Java,子进程和未读输出流:它何时死锁?
在使用Runtime.exec()
在java中创建子进程时,我知道必须填充输入/输出流以防止对子进程进行阻塞。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.这个死锁是OS中的一个错误吗?
当读取任何输出之前,父级尝试将太多数据发送到其子级的输入流时,会由于缓冲区大小有限而发生死锁。
考虑以下代码:
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("abcdefghijklmnopqrstuvwxyz\n");
}
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
的输出都可以放入OS缓冲区。但是,对于较大的值(> 10000应该足够),OS缓冲区将填满;在tr
命令中,对write
的调用将被阻塞,等待缓冲区被排空,并且Java代码正在写入的缓冲区将被填满(因为tr
被阻止,从而阻止其从输入读取) ,从而阻止我们的调用out.write
,导致死锁,其中两个进程正在等待写入未被主动读取的完整缓冲区。
这个死锁不是操作系统中的一个bug,因为进程间通信的有限缓冲区大小是故意的设计决定。另一种方法(无限缓冲区的大小)有一些缺点:
- 可能会耗尽内核内存
- 为了避免上述情况,如果缓冲区自动“溢出”到磁盘,这会导致不可预知的性能,并有可能填补磁盘。
顺便说一句,死锁也可能发生由于在进程缓冲区。假设,为了试图解决上述的死锁问题,我们将Java代码改为写一行,然后交替读一行。但是,it's common for Linux processes to not flush after every line when they're not writing directly to a terminal。因此,tr
可能会读取一行,并将其写入其libc输出缓冲区,而不是等待下一行被写入 - 而我们的Java代码将阻止等待tr
输出一行。
死锁总是涉及至少2个参与者。因此,不是单独的子进程可能会死锁,而是父进程和子进程的组合。
有关示例:
子过程要读取的线,并处理和打印结果。 父进程认为它可以先从子进程读取输出,然后才向其提供输入。
结果:死锁,两个进程都在等待来自另一个进程的输入。
因此,不,它不是操作系统中的错误,而是(父)过程中的逻辑错误。
我不确定这种问题是否由javadoc打算 - 您的问题描述的是并发环境中的一般同步问题,但与java/Process类无关。 javadoc还表示,这只会发生在“仅提供有限缓冲区大小的某些本地平台上”,而您描述的情况会阻止所有操作系统。 – MRalwasser 2012-02-21 15:59:51
@MRalwaser - 如果是这样的话,我确实会认为它是操作系统中的一个bug。 “及时”的含义并不明确。只要沟通按照正确的顺序完成,不论我是“及时”执行还是2天后执行,都无关紧要。 – Ingo 2012-02-21 16:07:03
+1 ... *(不是你的问题的答案,因此评论)* ...请注意,很久以前(当我读*“当Runtime.exec不会”* IIRC),我决定通过立即从Runtime.exec'ed shell脚本分支/退出并将第二个shell脚本的任何输出重定向到临时文件(然后从Java解析这些临时文件)来完全避免该问题。这是残酷的,但它的作品(我已经在Linux和OS X上做过...不知道Windows):) – TacticalCoder 2012-02-21 16:17:18