在分析github.com/hpcloud/tail 這個包的源碼的時候,發現這個包裏用於了一個另一個包,本身也沒有用過,可是這個包在tail這個包裏又起來很是大的做用git
當時並無徹底弄明白這個包的用法和做用,因此又花時間找了這個包的使用和相關文檔,其中看了https://blog.labix.org/2011/10/09/death-of-goroutines-under-control 這篇文章整理的挺好的,本身對這個文章進行了簡單的翻譯,下面這個文章中的使用是gopkg.in/tomb.v2github
不少人被go語言吸引的緣由是其很是好的併發性,以及channel, 輕量級線程(goroutine)等這些特性golang
而且你也會常常在社區或者其餘文章裏看到這樣一句話:算法
Do not communicate by sharing memory;數據庫
instead, share memory by communicating.網絡
這個模型很是合理,以這種方式處理問題,在設計算法時會產生顯著差別,但這個並非什麼新聞併發
What I address in this post is an open aspect we have today in Go related to this design: the termination of background activity.(不知道怎麼翻譯了)ide
做爲一個例子,咱們構建一個簡單的goroutine,經過一個channel 發送函數
type LineReader struct { Ch chan string r *bufio.Reader } func NewLineReader(r io.Reader) *LineReader { lr := &LineReader{ Ch: make(chan string), r: bufio.NewReader(r), } go lr.loop() return lr }
The type has a channel where the client can consume lines from, and an internal buffer
used to produce the lines efficiently. Then, we have a function that creates an initialized
reader, fires the reading loop, and returns. Nothing surprising there.oop
接着看看loop方法
func (lr *LineReader) loop() { for { line, err := lr.r.ReadSlice('\n') if err != nil { close(lr.Ch) return } lr.Ch <- string(line) } }
在這個loop中,咱們將從從緩衝器中獲取每行的內容,若是出現錯誤時關閉通道並中止,不然則將讀取的一行內容放到channel中
也許channel接受的那一方忙於其餘處理,而致使其會阻塞。 這個簡單的例子對於不少go開發者應該很是熟悉了
但這裏有兩個與終止邏輯相關的細節:首先錯誤信息被丟棄,而後沒法經過一種更加乾淨的方式從外部終端程序
固然,錯誤很容易記錄下來,可是若是咱們想要將它存儲數據庫中,或者經過網絡發送它,或者甚至考慮到它的性質,在很過狀況下
乾淨的中止也是很是有價值的
這並不是是一個很是難以作到的事情,可是今天沒有簡單一致的方法來處理,或者也許沒有,而go中的Tom包就是試圖解決這個問題的
這個模型很簡單:tomb跟蹤一個或者多個goroutines是活着的,仍是已經死了,以及死亡的緣由
爲了理解這個模型,咱們把上面的LineReader例子進行改寫:
type LineReader struct { Ch chan string r *bufio.Reader t tomb.Tomb } func NewLineReader(r io.Reader) *LineReader { lr := &LineReader{ Ch: make(chan string), r: bufio.NewReader(r), } lr.t.Go(lr.loop) return lr }
這裏有一些有趣的點:
首先,如今出現的錯誤結果與任何可能失敗的go函數或者方法同樣。
hen, the previously loose error is now returned, 標記這個goroutine終止的緣由,
最後這個通道的發送被調增,以便於無論goroutine由於上面緣由死亡都不會阻塞
A Tomb has both Dying and Dead channels returned by the respective methods, which are closed when the Tomb state changes accordingly. These channels enable explicit blocking until the state changes, and also to selectively unblock select statements in those cases, as done above.
經過上面的說的,咱們能夠簡單的引入Stop方法從外部同步請求清楚goroutine
func (lr *LineReader) Stop() error { lr.t.Kill(nil) return lr.t.Wait() }
在這種狀況下,Kill方法會將正在運行的goroutine從外部將其置於一個死亡狀態,而且Wait將阻塞直到goroutine經過
本身終止返回。 即便因爲內部錯誤,groutine已經死亡或者處於死亡狀態,此過程也會正常運行,由於只有第一次用一個實際的錯誤調用Kill被記錄爲goroutine死亡緣由。
The nil value provided to t.Kill is used as a reason when terminating cleanly without an actual error, and it causes Wait to return nil once the goroutine terminates, flagging a clean stop per common Go idioms.
關於gopkg.in/tomb.v2的官網說明的一段話:
The tomb package handles clean goroutine tracking and termination.
The zero value of a Tomb is ready to handle the creation of a tracked goroutine via its Go method, and then any tracked goroutine may call the Go method again to create additional tracked goroutines at any point.
If any of the tracked goroutines returns a non-nil error, or the Kill or Killf method is called by any goroutine in the system (tracked or not), the tomb Err is set, Alive is set to false, and the Dying channel is closed to flag that all tracked goroutines are supposed to willingly terminate as soon as possible.
Once all tracked goroutines terminate, the Dead channel is closed, and Wait unblocks and returns the first non-nil error presented to the tomb via a result or an explicit Kill or Killf method call, or nil if there were no errors.
It is okay to create further goroutines via the Go method while the tomb is in a dying state. The final dead state is only reached once all tracked goroutines terminate, at which point calling the Go method again will cause a runtime panic.
Tracked functions and methods that are still running while the tomb is in dying state may choose to return ErrDying as their error value. This preserves the well established non-nil error convention, but is understood by the tomb as a clean termination. The Err and Wait methods will still return nil if all observed errors were either nil or ErrDying.
在golang官網上看到了這樣一個例子,以爲用的挺好的就放這裏
package main import ( "gopkg.in/tomb.v1" "log" "sync" "time" ) type foo struct { tomb tomb.Tomb wg sync.WaitGroup } func (f *foo) task(id int) { for i := 0; i < 10; i++ { select { case <-time.After(1e9): log.Printf("task %d tick\n", id) case <-f.tomb.Dying(): log.Printf("task %d stopping\n", id) f.wg.Done() return } } } func (f *foo) Run() { f.wg.Add(10) for i := 0; i < 10; i++ { go f.task(i) } go func() { f.wg.Wait() f.tomb.Done() }() } func (f *foo) Stop() error { f.tomb.Kill(nil) return f.tomb.Wait() } func main() { var f foo f.Run() time.Sleep(3.5e9) log.Printf("calling stop\n") f.Stop() log.Printf("all done\n") }
在關於tomb這個包的說明上,說的也很是清楚,tomb包用於追蹤一個goroutine的聲明週期,如:as alive,dying or dead and the reason for its death
關於v1 版本官網的說明
The tomb package offers a conventional API for clean goroutine termination.
A Tomb tracks the lifecycle of a goroutine as alive, dying or dead, and the reason for its death.
The zero value of a Tomb assumes that a goroutine is about to be created or already alive. Once Kill or Killf is called with an argument that informs the reason for death, the goroutine is in a dying state and is expected to terminate soon. Right before the goroutine function or method returns, Done must be called to inform that the goroutine is indeed dead and about to stop running.
A Tomb exposes Dying and Dead channels. These channels are closed when the Tomb state changes in the respective way. They enable explicit blocking until the state changes, and also to selectively unblock select statements accordingly.
When the tomb state changes to dying and there's still logic going on within the goroutine, nested functions and methods may choose to return ErrDying as their error value, as this error won't alter the tomb state if provided to the Kill method. This is a convenient way to follow standard Go practices in the context of a dying tomb..
能夠從上面的文章以及使用例子上看出,tomb包是一個很是實用的一個包,後面會繼續整理一下關於tomb v1版本的源碼,看看人家是如何實現的,學習學習