[TOC]
## 7、動態保活Worker工作池設計
### 一、我們如何知道一個Goroutine已經死亡?
實際上,Go語言并沒有給我們暴露如何知道一個Goroutine是否存在的接口,如果要證明一個Go是否存在,可以在子Goroutine的業務中,定期寫向一個keep live的Channel,然后主Goroutine來發現當前子Go的狀態。Go語言在對于Go和Go之間沒有像進程和線程一樣有強烈的父子、兄弟等關系,每個Go實際上對于調度器都是一個獨立的,平等的執行流程。
>PS: 如果你是監控子線程、子進程的死亡狀態,就沒有這么簡單了,這里也要感謝go的調度器給我們提供的方便,我們既然用Go,就要基于Go的調度器來實現該模式。
那么,我們如何做到一個Goroutine已經死亡了呢?
#### 子Goroutine
可以通過給一個被監控的Goroutine添加一個`defer` ,然后`recover()` 捕獲到當前Goroutine的異常狀態,最后給主Goroutine發送一個死亡信號,通過`Channel`。
#### 主Goroutine
在`主Goroutine`上,從這個`Channel`讀取內容,當讀到內容時,就重啟這個`子Goroutine`,當然`主Goroutine`需要記錄`子Goroutine`的`ID`,這樣也就可以針對性的啟動了。
### 二、代碼實現
我們這里以一個工作池的場景來對上述方式進行實現。
`WorkerManager`作為`主Goroutine`, `worker`作為子`Goroutine`
> WorkerManager
```go
type WorkerManager struct {
//用來監控Worker是否已經死亡的緩沖Channel
workerChan chan *worker
// 一共要監控的worker數量
nWorkers int
}
//創建一個WorkerManager對象
func NewWorkerManager(nworkers int) *WorkerManager {
return &WorkerManager{
nWorkers:nworkers,
workerChan: make(chan *worker, nworkers),
}
}
//啟動worker池,并為每個Worker分配一個ID,讓每個Worker進行工作
func (wm *WorkerManager)StartWorkerPool() {
//開啟一定數量的Worker
for i := 0; i < wm.nWorkers; i++ {
i := i
wk := &worker{id: i}
go wk.work(wm.workerChan)
}
//啟動保活監控
wm.KeepLiveWorkers()
}
//保活監控workers
func (wm *WorkerManager) KeepLiveWorkers() {
//如果有worker已經死亡 workChan會得到具體死亡的worker然后 打出異常,然后重啟
for wk := range wm.workerChan {
// log the error
fmt.Printf("Worker %d stopped with err: [%v] \n", wk.id, wk.err)
// reset err
wk.err = nil
// 當前這個wk已經死亡了,需要重新啟動他的業務
go wk.work(wm.workerChan)
}
}
```
>worker
```go
type worker struct {
id int
err error
}
func (wk *worker) work(workerChan chan<- *worker) (err error) {
// 任何Goroutine只要異常退出或者正常退出 都會調用defer 函數,所以在defer中想WorkerManager的WorkChan發送通知
defer func() {
//捕獲異常信息,防止panic直接退出
if r := recover(); r != nil {
if err, ok := r.(error); ok {
wk.err = err
} else {
wk.err = fmt.Errorf("Panic happened with [%v]", r)
}
} else {
wk.err = err
}
//通知 主 Goroutine,當前子Goroutine已經死亡
workerChan <- wk
}()
// do something
fmt.Println("Start Worker...ID = ", wk.id)
// 每個worker睡眠一定時間之后,panic退出或者 Goexit()退出
for i := 0; i < 5; i++ {
time.Sleep(time.Second*1)
}
panic("worker panic..")
//runtime.Goexit()
return err
}
```
### 三、測試
>main
```go
func main() {
wm := NewWorkerManager(10)
wm.StartWorkerPool()
}
```
結果:
```bash
$ go run workmanager.go
Start Worker...ID = 2
Start Worker...ID = 1
Start Worker...ID = 3
Start Worker...ID = 4
Start Worker...ID = 7
Start Worker...ID = 6
Start Worker...ID = 8
Start Worker...ID = 9
Start Worker...ID = 5
Start Worker...ID = 0
Worker 9 stopped with err: [Panic happened with [worker panic..]]
Worker 1 stopped with err: [Panic happened with [worker panic..]]
Worker 0 stopped with err: [Panic happened with [worker panic..]]
Start Worker...ID = 9
Start Worker...ID = 1
Worker 2 stopped with err: [Panic happened with [worker panic..]]
Worker 5 stopped with err: [Panic happened with [worker panic..]]
Worker 4 stopped with err: [Panic happened with [worker panic..]]
Start Worker...ID = 0
Start Worker...ID = 2
Start Worker...ID = 4
Start Worker...ID = 5
Worker 7 stopped with err: [Panic happened with [worker panic..]]
Worker 8 stopped with err: [Panic happened with [worker panic..]]
Worker 6 stopped with err: [Panic happened with [worker panic..]]
Worker 3 stopped with err: [Panic happened with [worker panic..]]
Start Worker...ID = 3
Start Worker...ID = 6
Start Worker...ID = 8
Start Worker...ID = 7
...
...
```
我們會發現,無論子Goroutine是因為 panic()異常退出,還是Goexit()退出,都會被主Goroutine監聽到并且重啟。主要我們就能夠起到保活的功能了. 當然如果線程死亡?進程死亡?我們如何保證? 大家不用擔心,我們用Go開發實際上是基于Go的調度器來開發的,進程、線程級別的死亡,會導致調度器死亡,那么我們的全部基礎框架都將會塌陷。那么就要看線程、進程如何保活啦,不在我們Go開發的范疇之內了。
- 封面
- 第一篇:Golang修養必經之路
- 1、最常用的調試 golang 的 bug 以及性能問題的實踐方法?
- 2、Golang的協程調度器原理及GMP設計思想?
- 3、Golang中逃逸現象, 變量“何時棧?何時堆?”
- 4、Golang中make與new有何區別?
- 5、Golang三色標記+混合寫屏障GC模式全分析
- 6、面向對象的編程思維理解interface
- 7、Golang中的Defer必掌握的7知識點
- 8、精通Golang項目依賴Go modules
- 9、一站式精通Golang內存管理
- 第二篇:Golang面試之路
- 1、數據定義
- 2、數組和切片
- 3、Map
- 4、interface
- 5、channel
- 6、WaitGroup
- 第三篇、Golang編程設計與通用之路
- 1、流?I/O操作?阻塞?epoll?
- 2、分布式從ACID、CAP、BASE的理論推進
- 3、對于操作系統而言進程、線程以及Goroutine協程的區別
- 4、Go是否可以無限go? 如何限定數量?
- 5、單點Server的N種并發模型匯總
- 6、TCP中TIME_WAIT狀態意義詳解
- 7、動態保活Worker工作池設計