Swift命令行工具:主进程退出时终止子Process进程
解决Swift命令行工具主进程终止时子进程残留的问题
我完全懂你的困扰——手动终止主进程后,用Process启动的子进程(比如tail -f)还赖在系统里,确实挺闹心。这是因为默认情况下,子进程和主进程是独立的进程实体,主进程退出后子进程会被系统的init进程接管,不会自动跟着终止。下面结合你的现有代码,给你一套彻底解决的方案:
1. 监听终止信号,主动清理子进程
命令行工具被手动终止(Xcode停止按钮、终端Ctrl+C)时,会收到SIGINT、SIGTERM这类终止信号。我们可以注册信号处理逻辑,在收到信号时主动终止子进程,甚至连带它启动的所有子进程一起清理。
具体代码修改:
在你的MainClass里添加信号源存储和处理逻辑:
class MainClass { private var task: Process? private var errorHandler: FileHandle? private var outputHandler: FileHandle? // 保存信号源引用,防止被提前释放 private var signalSources: [DispatchSourceSignal] = [] init(...) { // 你的现有初始化代码(启动子进程、设置管道等) setupSignalHandlers() } private func setupSignalHandlers() { // 监听常见的终止信号 let terminationSignals: [Int32] = [SIGINT, SIGTERM, SIGQUIT] for signal in terminationSignals { let signalSource = DispatchSource.makeSignalSource(signal: signal, queue: DispatchQueue.global()) signalSource.setEventHandler { [weak self] in guard let self = self else { return } print("主进程收到终止信号,正在清理子进程...") // 先尝试优雅终止子进程 self.task?.terminate() // 关键:如果子进程还启动了孙子进程(比如sh启动tail),杀掉整个进程组 if let childPid = self.task?.processIdentifier { // 先发送SIGTERM尝试优雅退出 killpg(childPid, SIGTERM) // 0.5秒后强制发送SIGKILL,确保彻底终止 DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { killpg(childPid, SIGKILL) } } exit(signal) } signalSource.resume() self.signalSources.append(signalSource) } } // 可选:类销毁时也尝试清理子进程 deinit { task?.terminate() if let pid = task?.processIdentifier { killpg(pid, SIGTERM) } } // 你的其他现有方法... }
2. 为什么要用killpg?
你启动的是/bin/sh tail -f,这里sh是直接子进程,tail是sh启动的孙子进程。如果只调用task.terminate(),只会杀掉sh,tail会变成孤儿进程被init接管,继续运行。
killpg会给整个进程组发送信号,sh和它启动的tail属于同一个进程组,这样就能一次性清理所有相关进程,彻底解决残留问题。
3. 保留原有逻辑的兼容性
你之前用DispatchSemaphore处理子进程出错的逻辑可以完全保留,现在加上信号处理后,不管是子进程出错主动触发退出,还是主进程被手动终止,子进程都会被正确清理。
测试一下:在Xcode里运行工具后点击停止按钮,或者终端运行后按Ctrl+C,去活动监视器里看看tail和sh是不是都消失了~
内容的提问来源于stack exchange,提问作者Lupurus
相关产品推荐
相关产品推荐

