最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文搞懂Go?Exec?僵尸與孤兒進程

 更新時間:2022年02月28日 08:36:10   作者:WilburXu  
本文主要介紹了Go?Exec?僵尸與孤兒進程,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

最近,使用 golang 去管理本地應(yīng)用的生命周期,期間有幾個有趣的點,今天就一起看下。

場景一

我們來看看下面兩個腳本會產(chǎn)生什么問題:

創(chuàng)建兩個 shell 腳本

  • start.sh
#!/bin/sh
sh sub.sh
  • sub.sh
#!/bin/sh
n=0
while [ $n -le 100 ]
do
  echo $n
  let n++
  sleep 1
done

執(zhí)行腳本

輸出結(jié)果

$ ./start.sh 
0
1
2
...

進程關(guān)系

查看進程信息

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 進程還在執(zhí)行
  • sub.sh 進程的 PID 變成了 1

問題1:

sub.sh 這個進程現(xiàn)在屬于什么?

場景二

假設(shè)sub.sh 是實際的應(yīng)用, start.sh 是應(yīng)用的啟動腳本。

那么,golang 是如何管理他們的呢? 我們繼續(xù)看看下面 關(guān)于golang的場景。

在上面兩個腳本的基礎(chǔ)上,我們用golang 的 os/exec庫去調(diào)用 start.sh腳本

package main

import (
	"context"
	"log"
	"os"
	"os/exec"
	"time"
)

func main()  {
	cmd := exec.CommandContext(context.Background(), "./start.sh")

  // 將 start.sh 和 sub.sh 移到當(dāng)前目錄下
	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)
		}
	}
}

執(zhí)行程序

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

發(fā)現(xiàn) go 、 start.sh 、sub.sh 三個進程為同一個進程組(同一個 PGID)

父子關(guān)系為: main.go -> start.sh -> sub.sh

刪除 start.sh 的進程

實際場景,有可能啟動程序掛了,導(dǎo)致我們無法監(jiān)聽到執(zhí)行程序的情況,刪除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
  • 發(fā)現(xiàn)沒, start.sh 的 PPID 為1
  • 即使 start.sh 的 PPID變成了1 ,log.Println(cmd.Process.Pid) 還持續(xù)的輸出 .

問題2:

那如果 PPID為1 ,golang程序不就無法管理了嗎? 即使 sub.sh 退出也不知道了,那要如何處理?

問題分析

  • 兩個場景中, 都有一個共同的點,就是 PPID 為1,這妥妥的成為沒人要的娃了——孤兒進程

  • 場景二中,如果 cmd的沒有進程沒有被回收,go程序也無法管理,那么start.sh就成為了占著茅坑不拉屎的子進程——僵尸進程

那究竟什么是孤兒進程 和 僵尸進程?

孤兒進程

在類 UNIX 操作系統(tǒng)中,孤兒進程(Orphan Process)指:是在其父進程執(zhí)行完成或被終止后仍繼續(xù)運行的一類進程。

為避免孤兒進程退出時無法釋放所占用的資源而僵死,任何孤兒進程產(chǎn)生時都會立即為系統(tǒng)進程 init 或 systemd 自動接收為子進程,這一過程也被稱為收養(yǎng)。在此需注意,雖然事實上該進程已有init作為其父進程,但由于創(chuàng)建該進程的進程已不存在,所以仍應(yīng)稱之為孤兒進程。孤兒進程會浪費服務(wù)器的資源,甚至有耗盡資源的潛在危險。

解決&預(yù)防

  • 終止機制:強制殺死孤兒進程(最常用的手段);

  • 再生機制:服務(wù)器在指定時間內(nèi)查找調(diào)用的客戶端,若找不到則直接殺死孤兒進程;

  • 超時機制:給每個進程指定一個確定的運行時間,若超時仍未完成則強制終止之。若有需要,亦可讓進程在指定時間耗盡之前申請延時。

  • 進程組:因為父進程終止或崩潰都會導(dǎo)致對應(yīng)子進程成為孤兒進程,所以也無法預(yù)料一個子進程執(zhí)行期間是否會被“遺棄”。有鑒于此,多數(shù)類UNIX系統(tǒng)都引入了進程組以防止產(chǎn)生孤兒進程。

僵尸進程

在類 UNIX 操作系統(tǒng)中,僵尸進程(zombie process)指:完成執(zhí)行(通過exit系統(tǒng)調(diào)用,或運行時發(fā)生致命錯誤或收到終止信號所致),但在操作系統(tǒng)的進程表中仍然存在其進程控制塊,處于"終止?fàn)顟B(tài)"的進程。
正常情況下,進程直接被其父進程 wait 并由系統(tǒng)回收。而僵尸進程與正常進程不同,kill 命令對僵尸進程無效,并且無法回收,從而導(dǎo)致資源泄漏。

解決&預(yù)防

收割僵尸進程的方法是通過 kill 命令手工向其父進程發(fā)送SIGCHLD信號。如果其父進程仍然拒絕收割僵尸進程,則終止父進程,使得 init 進程收養(yǎng)僵尸進程。init 進程周期執(zhí)行 wait 系統(tǒng)調(diào)用收割其收養(yǎng)的所有僵尸進程。

查看進程詳情

# 列出進程
ps -l
  • USER:進程的所屬用戶
  • PID:進程的進程ID號
  • RSS:進程占用的固定的內(nèi)存量 (Kbytes)
  • S:查看進程狀態(tài)
  • CMD:進程對應(yīng)的實際程序

進程狀態(tài)(S)

  • R:運行 Runnable (on run queue) 正在運行或在運行隊列中等待
  • S:睡眠 Sleeping 休眠中,受阻,在等待某個條件的形成或接受到信號
  • I:空閑 Idle
  • Z:僵死 Zombie(a defunct process) 進程已終止,但進程描述符存在, 直到父進程調(diào)用wait4()系統(tǒng)調(diào)用后釋放
  • D:不可中斷 Uninterruptible sleep (ususally IO) 收到信號不喚醒和不可運行, 進程必須等待直到有中斷發(fā)生
  • T:終止 Terminate 進程收到SIGSTOP、SIGSTP、 SIGTIN、SIGTOU信號后停止運行運行
  • P:等待交換頁
  • W:無駐留頁 has no resident pages 沒有足夠的記憶體分頁可分配
  • X:死掉的進程

Go解決方案

采用 殺掉進程組(kill process group,而不是只 kill 父進程,在 Linux 里面使用的是 kill -- -PID) 與 進程wait方案,結(jié)果如下:

package main

import (
	"context"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"
)

func main() {

	ctx := context.Background()
	cmd := exec.CommandContext(ctx, "./start.sh")
  
        // 設(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)
	}

        // 監(jiān)聽進程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對系統(tǒng)監(jiān)聽,正如"僵死 Zombie(a defunct process) 進程已終止,但進程描述符存在, 直到父進程調(diào)用wait4()系統(tǒng)調(diào)用后釋放",所說一致。

總結(jié)

嚴(yán)格地來說,僵尸進程并不是問題的根源,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵尸進程的那個父進程。

因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵尸進程時,更應(yīng)該是在實際的開發(fā)過程中,思考如何避免僵尸進程的產(chǎn)生。

參考:

https://pkg.go.dev/syscall

https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/syscall/syscall_linux.go;l=279

https://pkg.go.dev/os/exec

到此這篇關(guān)于一文搞懂Go Exec 僵尸與孤兒進程 的文章就介紹到這了,更多相關(guān)Go Exec 僵尸與孤兒進程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go routine使用方法講解

    Go routine使用方法講解

    goroutine是Go語言提供的語言級別的輕量級線程,在我們需要使用并發(fā)時,我們只需要通過go關(guān)鍵字來開啟goroutine即可。這篇文章主要介紹了GoLang并發(fā)機制goroutine原理,感興趣的可以了解一下
    2023-01-01
  • Golang實現(xiàn)單元測試中的接口層

    Golang實現(xiàn)單元測試中的接口層

    接口層主要負責(zé)的就是請求的處理,最常見的就是?HTTP?請求的處理。這篇文章主要為大家介紹了Golang如何實現(xiàn)單元測試中的接口層,需要的可以參考一下
    2023-03-03
  • golang多維度排序及題解最長連續(xù)序列

    golang多維度排序及題解最長連續(xù)序列

    這篇文章主要為大家介紹了golang多維度排序及題解最長連續(xù)序列示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言獲取本機邏輯CPU數(shù)量的方法

    Go語言獲取本機邏輯CPU數(shù)量的方法

    這篇文章主要介紹了Go語言獲取本機邏輯CPU數(shù)量的方法,實例分析了runtime庫的操作技巧,需要的朋友可以參考下
    2015-03-03
  • golang之?dāng)?shù)據(jù)校驗的實現(xiàn)代碼示例

    golang之?dāng)?shù)據(jù)校驗的實現(xiàn)代碼示例

    這篇文章主要介紹了golang之?dāng)?shù)據(jù)校檢的實現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • GIN的路由以及傳參問題

    GIN的路由以及傳參問題

    本文主要介紹了GIN的路由以及傳參問題,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 詳解go-zero是如何做路由管理的

    詳解go-zero是如何做路由管理的

    go-zero 是一個微服務(wù)框架,包含了 web 和 rpc 兩大部分,而對于 web 框架來說,路由管理是必不可少的一部分,那么本文就來探討一下 go-zero 的路由管理是怎么做的吧
    2023-08-08
  • go?defer?return?panic?執(zhí)行順序示例詳解

    go?defer?return?panic?執(zhí)行順序示例詳解

    這篇文章主要介紹了go?defer?return?panic?執(zhí)行順序,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-01-01
  • Go設(shè)計模式原型模式考查點及使用詳解

    Go設(shè)計模式原型模式考查點及使用詳解

    這篇文章主要為大家介紹了Go設(shè)計模式原型模式考查點及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • golang線程安全的map實現(xiàn)

    golang線程安全的map實現(xiàn)

    這篇文章主要介紹了golang線程安全的map實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03

最新評論

安福县| 云浮市| 永定县| 吉水县| 会同县| 连平县| 道真| 夏津县| 屏东县| 义乌市| 新建县| 祥云县| 馆陶县| 泽库县| 平远县| 竹山县| 开江县| 合川市| 磴口县| 新泰市| 大埔区| 文登市| 玉树县| 柳河县| 澄迈县| 新建县| 富源县| 鄂尔多斯市| 大城县| 丰县| 广东省| 唐河县| 东海县| 罗山县| 门头沟区| 安乡县| 秦皇岛市| 洮南市| 蛟河市| 吕梁市| 安新县|