<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近,使用 golang
去管理本地應用的生命週期,期間有幾個有趣的點,今天就一起看下。
我們來看看下面兩個指令碼會產生什麼問題:
#!/bin/sh sh sub.sh
#!/bin/sh n=0 while [ $n -le 100 ] do echo $n let n++ sleep 1 done
輸出結果
$ ./start.sh
0
1
2
...
檢視程序資訊
ps -j USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND root 31758 31346 31758 0 1 S+ s000 0:00.00 /bin/sh ./start.sh root 31759 31758 31758 0 1 S+ s000 0:00.01 sh sub.sh
sub.sh
的 父程序(PPID)為 start.sh
的程序id(PID)
sub.sh
和 start.sh
兩個程序的 PGID
是同一個,( 屬一個行程群組)。
start.sh
的程序kill -9 31758 # 再檢視行程群組 ps -j ## 返回 USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND root 31759 1 31758 0 0 S s000 0:00.03 sh sub.sh
start.sh
程序不在了sub.sh
程序還在執行sub.sh
程序的 PID
變成了 1那sub.sh
這個程序現在屬於什麼?
假設sub.sh
是實際的應用, start.sh
是應用的啟動指令碼。
那麼,golang
是如何管理他們的呢? 我們繼續看看下面 關於golang
的場景。
在上面兩個指令碼的基礎上,我們用golang
的 os/exec
庫去呼叫 start.sh
指令碼
package main import ( "context" "log" "os" "os/exec" "time" ) func main() { cmd := exec.CommandContext(context.Background(), "./start.sh") // 將 start.sh 和 sub.sh 移到當前目錄下 cmd.Dir = "/Go/src/go-code/cmd/" cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { log.Printf("cmd.Start error %+v n", err) } for { select { default: log.Println(cmd.Process.Pid) time.Sleep(2 * time.Second) } } }
go run ./main.go
ps -j USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND root 45458 45457 45457 0 0 Ss+ s004 0:00.03 ...___1go_build_go_code_cmd root 45462 45458 45457 0 0 S+ s004 0:00.01 /bin/sh ./start.sh root 45463 45462 45457 0 0 S+ s004 0:00.03 sh sub.sh
發現 go
、 start.sh
、sub.sh
三個程序為同一個行程群組(同一個 PGID)
父子關係為: main.go
-> start.sh
-> sub.sh
start.sh
的程序實際場景,有可能啟動程式掛了,導致我們無法監聽到執行程式的情況,刪除start.sh
程序,模擬下場景 :
kill -9 45462
ps -j USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND root 45458 45457 45457 0 0 Ss+ s004 0:00.03 ...___1go_build_go_code_cmd root 45462 1 45457 0 0 S+ s004 0:00.01 (bash) root 45463 45462 45457 0 0 S+ s004 0:00.03 sh sub.sh
start.sh
的 PPID
為1start.sh
的 PPID
變成了1 ,log.Println(cmd.Process.Pid)
還持續的輸出 .那如果 PPID
為1 ,golang
程式不就無法管理了嗎? 即使 sub.sh 退出也不知道了,那要如何處理?
兩個場景中, 都有一個共同的點,就是 PPID
為1,這妥妥的成為沒人要的娃了——孤兒程序
場景二中,如果 cmd
的沒有程序沒有被回收,go
程式也無法管理,那麼start.sh
就成為了佔著茅坑不拉屎的子程序——殭屍程序
那究竟什麼是孤兒程序
和 殭屍程序
?
在類 UNIX
作業系統中,孤兒程序(Orphan Process)指:是在其父程序執行完成或被終止後仍繼續執行的一類程序。
為避免孤兒程序退出時無法釋放所佔用的資源而僵死,任何孤兒程序產生時都會立即為系統程序 init
或 systemd
自動接收為子程序,這一過程也被稱為收養
。在此需注意,雖然事實上該程序已有init
作為其父程序,但由於建立該程序的程序已不存在,所以仍應稱之為孤兒程序
。孤兒程序會浪費伺服器的資源,甚至有耗盡資源的潛在危險。
終止機制:強制殺死孤兒程序(最常用的手段);
再生機制:伺服器在指定時間內查詢呼叫的使用者端,若找不到則直接殺死孤兒程序;
超時機制:給每個程序指定一個確定的執行時間,若超時仍未完成則強制終止之。若有需要,亦可讓程序在指定時間耗盡之前申請延時。
行程群組:因為父程序終止或崩潰都會導致對應子程序成為孤兒程序,所以也無法預料一個子程序執行期間是否會被“遺棄”。有鑑於此,多數類UNIX系統都引入了行程群組以防止產生孤兒程序。
在類 UNIX
作業系統中,殭屍程序(zombie process)指:完成執行(通過exit系統呼叫,或執行時發生致命錯誤或收到終止訊號所致),但在作業系統的程序表中仍然存在其過程控制塊,處於"終止狀態"的程序。
正常情況下,程序直接被其父程序 wait
並由系統回收。而殭屍程序與正常程序不同,kill
命令對殭屍程序無效,並且無法回收,從而導致資源洩漏。
收割殭屍程序的方法是通過 kill
命令手工向其父程序傳送SIGCHLD訊號。如果其父程序仍然拒絕收割殭屍程序,則終止父程序,使得 init
程序收養殭屍程序。init
程序週期執行 wait
系統呼叫收割其收養的所有殭屍程序。
# 列出程序 ps -l
採用 殺掉行程群組(kill process group,而不是隻 kill 父程序,在 Linux 裡面使用的是 kill -- -PID
) 與 程序wait方案,結果如下:
package main import ( "context" "log" "os" "os/exec" "syscall" "time" ) func main() { ctx := context.Background() cmd := exec.CommandContext(ctx, "./start.sh") // 設定行程群組 cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, } cmd.Dir = "/Users/Wilbur/Project/Go/src/go-code/cmd/" cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { log.Printf("cmd.Start error %+v n", err) } // 監聽程序wait errCmdCh := make(chan error, 1) go func() { errCmdCh <- cmd.Wait() }() for { select { case <-ctx.Done(): log.Println("ctx.done") pid := cmd.Process.Pid if err := syscall.Kill(-1*pid, syscall.SIGKILL); err != nil { return } case err := <-errCmdCh: log.Printf("errCmdCh error %+v n", err) return default: log.Println(cmd.Process.Pid) time.Sleep(2 * time.Second) } } }
剖析 cmd.Wait()
原始碼
在 os/exec_unix
下:
var ( status syscall.WaitStatus rusage syscall.Rusage pid1 int e error ) for { pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage) if e != syscall.EINTR { break } }
進行了 syscall.Wait4
對系統監聽,正如"僵死 Zombie(a defunct process) 程序已終止,但程序描述符存在, 直到父程序呼叫wait4()系統呼叫後釋放",所說一致。
嚴格地來說,殭屍程序並不是問題的根源,罪魁禍首是產生出大量殭屍程序的那個父程序。
因此,當我們尋求如何消滅系統中大量的殭屍程序時,更應該是在實際的開發過程中,思考如何避免殭屍程序的產生。
https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/syscall/syscall_linux.go;l=279
到此這篇關於一文搞懂Go Exec 殭屍與孤兒程序 的文章就介紹到這了,更多相關Go Exec 殭屍與孤兒程序內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45