tl;博士
第一种方式。
如果一个进程启动一个子进程并等待它完成(示例),则无需特别注意。
如果一个进程启动一个子进程并可能提前终止它,它应该在一个新的进程组中启动该子进程并向该组发送信号。
细节
奇怪的是,这适用的频率(比如,每个 shell 脚本),我找不到关于约定/最佳实践的好答案。
一些扣除:
创建和发送信号进程组非常常见。特别是,交互式 shell 可以做到这一点。所以(除非它采取额外的步骤来防止它)一个进程的子进程可以在任何时候收到 SIGINT 信号,在非常正常的情况下。
为了支持尽可能少的范式,总是依赖它似乎是有意义的。
这意味着第一种样式是可以的,并且进程管理的负担放在了在常规操作期间故意终止其子进程的进程(这相对较少见)。
另请参阅下面的“案例研究:超时”以获取更多证据。
怎么做
虽然问题的角度来自普通被调用程序的要求,但这个答案提出了一个问题:如何在新进程组中启动进程(在非普通情况下希望过早中断进程)?
这在某些语言中很容易,而在其他语言中则很困难。我创建了一个实用程序 run-pgrp
来协助后一种情况。
#!/usr/bin/env python3
# Run the command in a new process group, and forward signals.
import os
import signal
import sys
pid = os.fork()
if not pid:
os.setpgid(0, 0)
os.execvp(sys.argv[1], sys.argv[1:])
def receiveSignal(sig, frame):
os.killpg(pid, sig)
signal.signal(signal.SIGINT, receiveSignal)
signal.signal(signal.SIGTERM, receiveSignal)
_, status = os.waitpid(-1, 0)
sys.exit(status)
调用者可以使用它来包装它过早终止的进程。
Node.js 示例:
const childProcess = require("child_process");
(async () => {
const process = childProcess.spawn(
"run-pgrp",
["bash", "-c", "echo start; sleep 600; echo done"],
{ stdio: "inherit" }
);
/* leaves orphaned process
const process = childProcess.spawn(
"bash",
["-c", "echo start; sleep 600; echo done"],
{ stdio: "inherit" }
);
*/
await new Promise(res => setTimeout(res, /* 1s */ 1000));
process.kill();
if (process.exitCode == null) {
await new Promise(res => process.on("exit", res));
}
})();
在这个程序结束时,休眠进程终止。如果直接调用的命令不带run-pgrp
,则睡眠进程继续运行。
案例研究:超时
GNUtimeout
实用程序是一个可以终止其子进程的程序。
值得注意的是,它在一个新的进程组中运行子进程。这支持了这样的结论,即在潜在的中断之前应该创建一个新的进程组。
然而,有趣的是,超时也将自己置于进程组中,以避免转发信号的复杂性,但会导致一些奇怪的行为。https://unix.stackexchange.com/a/57692/56781
例如,在交互式 shell 中,运行
bash -c "echo start; timeout 600 sleep 600; echo done"
尝试打断这个 (Ctrl+C)。它没有响应,因为timeout
从来没有收到信号!
相反,我的run-pgrp
实用程序将自己保留在原始进程组中,并将 SIGINT/SIGTERM 转发给子组。