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

发布于 2025-01-07 12:09:46 字数 451 浏览 0 评论 0原文

当使用 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. 这个死锁是操作系统的错误吗?

When creating sub-processes in java using Runtime.exec(), I am aware that I have to fill the input/drain the output streams to prevent blocking of the subprocess.

Interestingly, the javadoc of Process states a little bit more:

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

I am wondering that in this situation the subprocess can also deadlock!

Questions:
1. Under which conditions does it deadlock?
2. Why does it deadlock?
3. Can you provide a short example program which shows this deadlock?
4. Is this deadlock a bug in the OS?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

墨落成白 2025-01-14 12:09:46

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

考虑以下代码:

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 的所有输出都可以放入操作系统缓冲区中。然而,对于较大的值(>10000 应该足够了),操作系统缓冲区将被填满;在 tr 命令中,对 write 的调用将被阻塞,等待缓冲区被耗尽,然后 Java 代码正在写入的缓冲区将被填满(因为 tr 被阻止,阻止它从输入中读取),进而阻止我们out.write 的调用,导致死锁两个进程都在等待写入未主动读取的已满缓冲区。

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

  • 可能会耗尽内核内存
  • 为了避免上述情况,如果缓冲区自动“溢出”到磁盘,则可能导致性能不可预测,并可能填满磁盘。

另外,进程内缓冲区也可能导致死锁。假设,为了尝试解决上述死锁,我们将 Java 代码更改为交替写入一行,然后读取一行。然而,Linux 进程在不直接写入终端时,通常不会在每一行后刷新< /a>.因此,tr 可能会读取一行,并将其写入其 libc 输出缓冲区,然后阻塞等待下一行被写入 - 而我们的 Java 代码将阻塞等待 tr 输出一行。

Deadlock can occur due to limited buffer sizes when the parent tries to send too much data to the input stream of its child before reading any of the output.

Consider this code:

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);
}

For small values for lines, it will work fine; all of tr's output can fit in the OS buffer before we start reading it. However, for large values (>10000 ought to be sufficient), the OS buffer will fill up; within the tr command, calls to write will block, waiting for the buffer to be drained, and in turn, the buffer that the Java code is writing to will fill up (because tr is be blocked, preventing it from reading from its input), in turn blocking our calls to out.write, leading to a deadlock where both processes are waiting to write into full buffers that aren't being actively read from.

This deadlock is not a bug in the OS, as limited buffer sizes for inter-process communication are a deliberate design decision. The alternative (unlimited buffer sizes) has some downsides:

  • Can exhaust kernel memory
  • To avoid the above, if buffers automatically "spill over" to disk, that can lead to unpredictable performance and can potentially fill up the disk.

As an aside, deadlock can also occur due to in-process buffers. Suppose that, to attempt to address the deadlock above, we changed our Java code to write one line, then read one line, alternatingly. However, it's common for Linux processes to not flush after every line when they're not writing directly to a terminal. So tr might read a line, and write it to its libc output buffer, and than block waiting for the next line to be written--and our Java code will block waiting for tr to output a line.

吾家有女初长成 2025-01-14 12:09:46

死锁总是涉及至少 2 个参与者。所以可能死锁的并不是子进程本身,而是父进程和子进程的组合。

举个例子:

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

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

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

Deadlock involves always at least 2 participants. So it's not the subprocess alone that may dead lock, but the combination of the parent process and the subprocess.

For an example:

Subprocess wants to read a line, process it and print the result.
Parent process thinks it can read output from the subprocess first, and only then provide input to it.

Result: deadlock, both processes are waiting for input from the other.

And, hence, no, it's not a bug in the OS but a logical error in the (parent) process.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文