golang 設(shè)置進(jìn)程退出時kill所有子進(jìn)程的三種方法
在 Go 語言中使用 os/exec.Command 啟動子進(jìn)程時,默認(rèn)情況下子進(jìn)程不會隨父進(jìn)程(當(dāng)前進(jìn)程)的退出而自動終止,因為子進(jìn)程會被系統(tǒng)(如 init 或系統(tǒng)進(jìn)程管理器)收養(yǎng)并繼續(xù)運行。要實現(xiàn)“父進(jìn)程退出時自動終止子進(jìn)程”的功能,需要根據(jù)操作系統(tǒng)采用不同的策略。沒有完美的跨平臺標(biāo)準(zhǔn)解決方案,但以下是常見實現(xiàn)方式,包括手動處理信號和平臺特定機制。以下解釋逐步如何實現(xiàn),并提供代碼示例。
1.通用方式:捕獲信號并手動終止子進(jìn)程(跨平臺,適用于正常退出和可捕獲信號)
這是一種簡單、跨平臺的做法:父進(jìn)程監(jiān)聽退出信號(如 SIGINT、SIGTERM),在收到信號時主動殺死子進(jìn)程。這種方式適用于父進(jìn)程正常退出或被可捕獲信號終止的情況,但如果父進(jìn)程被 kill -9 (SIGKILL) 強制殺死,則無效(因為 SIGKILL 無法捕獲)。
步驟:
- 使用
signal.Notify監(jiān)聽信號。 - 啟動子進(jìn)程后,記錄
PID。 - 在信號處理中逐個終止子進(jìn)程(如果需要殺死子進(jìn)程的子進(jìn)程,可使用進(jìn)程組,詳見下文)。
代碼示例:
package main
import (
"context"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
func main() {
// 啟動子進(jìn)程,例如運行 "sleep 60"
cmd := exec.Command("sleep", "60")
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// 監(jiān)聽信號
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// goroutine 處理信號
go func() {
sig := <-sigs
fmt.Printf("Received signal: %v\n", sig)
if cmd.Process != nil {
if err := cmd.Process.Kill(); err != nil {
fmt.Println("Kill error:", err)
}
}
os.Exit(0)
}()
// 等待子進(jìn)程正常結(jié)束(可選,如果子進(jìn)程有自己的退出邏輯)
if err := cmd.Wait(); err != nil {
fmt.Println("Wait error:", err)
}
}
擴(kuò)展:使用進(jìn)程組殺死子進(jìn)程及其后代(Unix-like 系統(tǒng)):
要殺死整個進(jìn)程樹:
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// 在殺死時:
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
2.Linux 特定:使用 Pdeathsig 自動發(fā)送信號
在 Linux 上,可以使用 syscall.SysProcAttr.Pdeathsig 設(shè)置當(dāng)父進(jìn)程(或創(chuàng)建線程)死亡時,內(nèi)核自動向子進(jìn)程發(fā)送指定信號(如 SIGTERM 或 SIGKILL)。這實現(xiàn)了“自動”終止,即使父進(jìn)程被 SIGKILL 殺死。
注意:
- 只適用于 Linux(基于 prctl(PR_SET_PDEATHSIG))。
- 如果子進(jìn)程是多線程的,可能有邊緣問題(見 Go issue #27505)。
- 子進(jìn)程需要能響應(yīng)信號(例如,如果它忽略 SIGTERM,則用 SIGKILL)。
代碼示例:
package main
import (
"fmt"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sleep", "60")
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL, // 或 syscall.SIGTERM
}
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// 父進(jìn)程可以繼續(xù)其他工作,或直接退出測試
// cmd.Wait() // 可選
}
3.Windows 特定:使用 Job Object 自動終止
在 Windows 上,沒有直接等價于 Pdeathsig 的機制,但可以使用 Windows Job Object 將進(jìn)程分組,并設(shè)置 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 標(biāo)志:當(dāng) Job Object 的最后一個句柄關(guān)閉(父進(jìn)程退出)時,自動殺死所有關(guān)聯(lián)進(jìn)程。
注意:
- Go 標(biāo)準(zhǔn)庫不直接支持,需要使用
golang.org/x/sys/windows包調(diào)用 Windows API。 - 需要安裝依賴:
go get golang.org/x/sys/windows。 - 這會終止子進(jìn)程及其后代。
代碼示例(簡化版,需要處理錯誤和清理):
package main
import (
"fmt"
"os/exec"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func main() {
// 創(chuàng)建 Job Object
job, err := windows.CreateJobObject(nil, nil)
if err != nil {
fmt.Println("CreateJobObject error:", err)
return
}
defer windows.CloseHandle(job)
// 設(shè)置 Kill on Job Close
var info windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION
info.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
err = windows.SetInformationJobObject(
job,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
)
if err != nil {
fmt.Println("SetInformationJobObject error:", err)
return
}
// 將當(dāng)前進(jìn)程分配到 Job Object(子進(jìn)程會繼承)
err = windows.AssignProcessToJobObject(job, windows.CurrentProcess())
if err != nil {
fmt.Println("AssignProcessToJobObject error:", err)
return
}
// 啟動子進(jìn)程
cmd := exec.Command("timeout", "/t", "60") // Windows 等價于 sleep
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// cmd.Wait() // 可選
}
說明:子進(jìn)程啟動后會自動關(guān)聯(lián) Job Object(因為父進(jìn)程已關(guān)聯(lián))。父進(jìn)程退出時,Job Object 關(guān)閉,子進(jìn)程被終止。
其他注意
- 跨平臺實現(xiàn):可以使用條件編譯(
//go:build linux等)來區(qū)分平臺特定代碼。對于通用場景,優(yōu)先使用信號捕獲方式。 - 測試:在 Unix 上用
kill <pid>測試;在 Windows 上用 Task Manager 殺死父進(jìn)程。 - 局限性:沒有方式能 100% 保證在所有情況下(如父進(jìn)程崩潰)自動終止,尤其是跨平臺。如果子進(jìn)程是 daemon 或忽略信號,可能需要額外處理。
到此這篇關(guān)于golang 設(shè)置進(jìn)程退出時kill所有子進(jìn)程的三種方法的文章就介紹到這了,更多相關(guān)Golang殺死子進(jìn)程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Gin框架搭建一個Go Web應(yīng)用程序的方法詳解
在本文中,我們將要實現(xiàn)一個簡單的 Web 應(yīng)用程序,通過 Gin 框架來搭建,主要支持用戶注冊和登錄,用戶可以通過注冊賬戶的方式創(chuàng)建自己的賬號,并通過登錄功能進(jìn)行身份驗證,感興趣的同學(xué)跟著小編一起來看看吧2023-08-08
linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實例分析了Go語言使用linux的系統(tǒng)命令ps來分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03
go語言實現(xiàn)一個簡單的http客戶端抓取遠(yuǎn)程url的方法
這篇文章主要介紹了go語言實現(xiàn)一個簡單的http客戶端抓取遠(yuǎn)程url的方法,實例分析了Go語言http操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03

