日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何避免 Go 命令行执行产生“孤儿”进程?

發布時間:2024/8/23 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何避免 Go 命令行执行产生“孤儿”进程? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介:?在 Go 程序當中,如果我們要執行命令時,通常會使用 exec.Command ,也比較好用,通常狀況下,可以達到我們的目的,如果我們邏輯當中,需要終止這個進程,則可以快速使用 cmd.Process.Kill() 方法來結束進程。但當我們要執行的命令會啟動其他子進程來操作的時候,會發生什么情況?

作者 | 昕希
來源 | 阿里技術公眾號

在 Go 程序當中,如果我們要執行命令時,通常會使用 exec.Command ,也比較好用,通常狀況下,可以達到我們的目的,如果我們邏輯當中,需要終止這個進程,則可以快速使用 cmd.Process.Kill() 方法來結束進程。但當我們要執行的命令會啟動其他子進程來操作的時候,會發生什么情況?

一 孤兒進程的產生

測試小程序:

func kill(cmd *exec.Cmd) func() {return func() {if cmd != nil {cmd.Process.Kill()}} }func main() {cmd := exec.Command("/bin/bash", "-c", "watch top >top.log")time.AfterFunc(1*time.Second, kill(cmd))err := cmd.Run()fmt.Printf("pid=%d err=%s\n", cmd.Process.Pid, err) }

執行小程序:

go run main.gopid=27326 err=signal: killed

查看進程信息:

ps -jUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND king 24324 1 24303 0 0 S s012 0:00.01 watch top

可以看到這個 "watch top" 的 PPID 為 1,說明這個進程已經變成了 “孤兒” 進程。

那為什么會這樣,這并不符合我們預期,那么可以從 Go 的文檔中找到答案:

二 通過進程組來解決掉所有子進程

在 linux 當中,是有會話、進程組和進程組的概念,并且 Go 也是使用 linux 的 kill(2) 方法來發送信號的,那么是否可以通過 kill 來將要結束進程的子進程都結束掉?

linux 的 kill(2) 的定義如下:

并在方法的描述中,可以看到如下內容:

如果 pid 為正數的時候,會給指定的 pid 發送 sig 信號,如果 pid 為負數的時候,會給這個進程組發送 sig 信號,那么我們可以通過進程組來將所有子進程退出掉?改一下 Go 程序中 kill 方法:

func kill(cmd *exec.Cmd) func() {return func() {if cmd != nil {// cmd.Process.Kill()syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)}} }func main() {cmd := exec.Command("/bin/bash", "-c", "watch top >top.log")time.AfterFunc(1*time.Second, kill(cmd))err := cmd.Run()fmt.Printf("pid=%d err=%s\n", cmd.Process.Pid, err) }

再次執行:

go run main.go

會發現程序卡住了,我們來看一下當前執行的進程:

ps -jUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND king 27655 91597 27655 0 1 S+ s012 0:01.10 go run main.go king 27672 27655 27655 0 1 S+ s012 0:00.03 ..../exe/main king 27673 27672 27655 0 1 S+ s012 0:00.00 /bin/bash -c watch top >top.log king 27674 27673 27655 0 1 S+ s012 0:00.01 watch top

可以看到我們 go run 產生了一個子進程 27672(command 那里是 go 執行的臨時目錄,比較長,因此添加了省略號),27672 產生了 27673(watch top >top.log)進程,27673 產生了 27674(watch top)進程。那為什么沒有將這些子進程都關閉掉呢?

其實之類犯了一個低級錯誤,從上圖中,我們可以看到他們的進程組 ID 為 27655,但是我們傳遞的是 cmd 的 id 即 27673,這個并不是進程組的 ID,因此程序并沒有 kill,導致 cmd.Run() 一直在執行。

在 Linux 中,進程組中的第一個進程,被稱為進程組 Leader,同時這個進程組的 ID 就是這個進程的 ID,從這個進程中創建的其他進程,都會繼承這個進程的進程組和會話信息;從上面可以看出 go run main.go 程序 PID 和 PGID 同為 27655,那么這個進程就是進程組 Leader,我們不能 kill 這個進程組,除非想“自殺”,哈哈哈。

那么我們給要執行的進程,新建一個進程組,在 Kill 不就可以了嘛。在 linux 當中,通過 setpgid 方法來設置進程組 ID,定義如下:

如果將 pid 和 pgid 同時設置成 0,也就是 setpgid(0,0),則會使用當前進程為進程組 leader 并創建新的進程組。

那么在 Go 程序中,可以通過 cmd.SysProcAttr 來設置創建新的進程組,修改后的代碼如下:

func kill(cmd *exec.Cmd) func() {return func() {if cmd != nil {// cmd.Process.Kill()syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)}} }func main() {cmd := exec.Command("/bin/bash", "-c", "watch top >top.log")cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true,}time.AfterFunc(1*time.Second, kill(cmd))err := cmd.Run()fmt.Printf("pid=%d err=%s\n", cmd.Process.Pid, err) }

再次執行:

go run main.gopid=29397 err=signal: killed

再次查看進程:

ps -jUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND

發現 watch 的進程都不存在了,那我們在看看是否還會有孤兒進程:

# 由于我測試的環境是mac,因此這個腳本只能在mac執行 ps -j | head -1;ps -j | awk '{if ($3 ==1 && $1 !="root"){print $0}}' | headUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND

已經沒有孤兒進程了,問題至此已經完全解決。

三 子進程監聽父進程是否退出(只能在 linux 下執行)

假設要調用的程序也是我們自己寫的其他應用程序,那么可以使用 Linux 的 prctl 方法來處理, prctl 方法的定義如下:

這個方法有一個重要的 option:PR_SET_PDEATHSIG,通過這個來接收父進程的退出。

讓我們來再次構造一個有問題的程序。

有兩個文件,分別為 main.go 和 child.go 文件,main.go 會調用 child.go 文件。

main.go 文件:

package mainimport ("os/exec" )func main() {cmd := exec.Command("./child")cmd.Run() }

child.go 文件:

package mainimport ("fmt""time" )func main() {for {time.Sleep(200 * time.Millisecond)fmt.Println(time.Now())} }

在 Linux 環境中分別編譯這兩個文件:

// 編譯 main.go 生成 main 二進制文件 go build -o main main.go// 編譯 child.go 生成 child 二進制文件 go build -o child child.go

執行 main 二進制文件:

./main &

查看他們的進程:

ps -efUID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash root 11514 1 0 12:12 pts/0 00:00:00 ./main root 11520 11514 0 12:12 pts/0 00:00:00 ./child

可以看到 main 和 child 的進程,child 是 main 的子進程,我們將 main 進程 kill 掉,在查看進程狀態:

kill -9 11514ps -efUID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash root 11520 1 0 12:12 pts/0 00:00:00 ./child

我們可以看到 child 的進程,他的 PPID 已經變成了 1,說明這個進程已經變成了孤兒進程。

那接下來我們可以使用 PR_SET_PDEATHSIG 來保證父進程退出,子進程也退出,大致方式有兩種:使用 CGO 調用和使用 syscall.RawSyscall 來調用。

1 使用 CGO

將 child 修改成如下內容:

程序中,使用 CGO,為了簡單的展示,在 Go 文件中編寫了 C 的 killTest 方法,并調用了 prctl 方法,然后在 Go 程序中調用 killTest 方法,讓我們重新編譯執行一下,再看看進程:

go build -o child child.go ./main & ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash root 11663 1 0 12:28 pts/0 00:00:00 ./main root 11669 11663 0 12:28 pts/0 00:00:00 ./child

再次 kill 掉 main,并查看進程:

kill -9 11663 ps -efUID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash

可以看到 child 的進程也已經退出了,說明 CGO 調用的 prctl 生效了。

2 syscall.RawSyscall 方法

也可以采用 Go 中提供的 syscall.RawSyscall 方法來替代調用 CGO,在 Go 的文檔中,可以查看到 syscall 包中定義的常量(查看 linux,如果是本地 godoc,需要指定 GOOS=linux),可以看到我們要用的幾個常量以及他們對應的數值:

// 其他內容省略掉了 const(....PR_SET_PDEATHSIG = 0x1.... )const( .....SYS_PRCTL = 157..... )

其中 PR_SET_PDEATHSIG 操作的值為 1,SYS_PRCTL 的值為 157,那么將 child.go 修改成如下內容:

package mainimport ("fmt""os""syscall""time" )func main() {_, _, errno := syscall.RawSyscall(uintptr(syscall.SYS_PRCTL), uintptr(syscall.PR_SET_PDEATHSIG), uintptr(syscall.SIGKILL), 0)if errno != 0 {os.Exit(int(errno))}for {time.Sleep(200 * time.Millisecond)fmt.Println(time.Now())} }

再次編譯并執行:

go build -o child child.go ./main & ps -efUID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash root 12208 1 0 12:46 pts/0 00:00:00 ./main root 12214 12208 0 12:46 pts/0 00:00:00 ./child

將 main 進程結束掉:

kill -9 12208 ps -efUID PID PPID C STIME TTY TIME CMD root 1 0 0 06:05 pts/0 00:00:00 /bin/bash

child 進程已經退出了,也達成了最終效果。

四 總結

當我們使用 Go 程序執行其他程序的時候,如果其他程序也開啟了其他進程,那么在 kill 的時候可能會把這些進程變成孤兒進程,一直執行并滯留在內存中。當然,如果我們程序非法退出,或者被 kill 調用,也會導致我們執行的進程變成孤兒進程,那么為了解決這個問題,從兩個思路來解決:

  • 給要執行的程序創建新的進程組,并調用 syscall.Kill,傳遞負值 pid 來關閉這個進程組中所有的進程(比較完美的解決方法)。
  • 如果要調用的程序也是我們自己編寫的,那么可以使用 PR_SET_PDEATHSIG 來感知父進程退出,那么這種方式需要調用 Linxu 的 prctrl,可以使用 CGO 的方式,也可以使用 syscall.RawSyscall 的方式。

但不管使用哪種方式,都只是提供了一種思路,在我們編寫服務端服務程序的時候,需要特殊關注,防止孤兒進程消耗服務器資源。

原文鏈接
本文為阿里云原創內容,未經允許不得轉載。

總結

以上是生活随笔為你收集整理的如何避免 Go 命令行执行产生“孤儿”进程?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欲色网站 | 欧美激情精品 | 西西人体高清44rt·net | 国产67194 | 日韩av一二三 | 成av在线| 熟妇高潮喷沈阳45熟妇高潮喷 | 日韩不卡在线视频 | 国产综合亚洲精品一区二 | 在线黄网站| 免费黄色入口 | 69视频在线免费观看 | 久草青青草 | 久久国产精品免费 | 少妇高清精品毛片在线视频 | 欧美亚洲国产精品 | 自拍第一页 | 婷婷网五月天 | 中文字幕乱码一二三区 | 亚洲国产网址 | 色就是色亚洲色图 | 超碰导航 | 麻豆av在线看 | 国产乱码精品一区二区三区精东 | 激情 小说 亚洲 图片 伦 | 99re这里 | 涩涩视频在线免费看 | 免费99视频 | 国产日韩av在线 | 欧美精品1区2区 | 欧美在线视频一区二区 | 最新av网站在线观看 | 国产精品无码一本二本三本色 | 亚洲AV成人无码电影在线观看 | 欧美在线视频网 | 国产一级特黄毛片 | 中日韩中文字幕 | 不卡日本视频 | 天天干影院 | 国产手机视频在线 | 99精品久久久久久久 | 亚洲福利视频一区二区三区 | 深爱激情av | 欧美一区二区三区久久精品 | 毛片.com| 毛片无遮挡高清免费观看 | 国产成人小视频在线观看 | 97伊人| 在线播放亚洲 | 91毛片观看| 欧美乱插| 黄色一级片免费播放 | 欧美激情喷水 | 久久er99热精品一区二区 | 干美女少妇 | 中文字幕av在线播放 | 欧美国产成人精品一区二区三区 | 毛片免费一区二区三区 | 国产主播福利在线 | 小蝌蚪视频色 | 成人免费毛片日本片视频 | 国外亚洲成av人片在线观看 | 一级成人黄色片 | 手机在线看黄色 | 国产一卡二卡三卡四卡 | 99er久久| 亚色成人 | 日日干夜夜艹 | 国产在线视频99 | 日韩一区二区三区免费 | 第一毛片 | 亚洲最新在线 | 欧美成人黄色小视频 | 国产黑丝一区二区 | 欧美一区二区三区久久 | 成人免费aaa | 好吊妞无缓冲视频观看 | 黄色激情网址 | 秋霞三区 | 91新视频| 五月激情婷婷在线 | 日韩精品在线观看中文字幕 | 欧美怡红院视频一区二区三区 | 日本少妇ⅹxxxxx视频 | 精品人妻一区二区三区视频 | 久久久久一区二区精码av少妇 | 91tv在线| 国产精品一区二区三区在线免费观看 | 亚洲视频www | 黄色美女av | 女同调教视频 | 韩国三级bd高清中字2021 | 中文有码在线观看 | 操极品 | 中文字幕第315页 | www在线播放 | 亚洲性激情| 美女扒开内看个够网站 | 91精品国产91久久久久青草 |