1. 什么是 Context?
在 Go 1.7 版本之前,context 還是非編制的,它存在于 golang.org/x/net/context 包中。
后來,Golang 團隊發現 context 還挺好用的,就把 context 收編了,在 Go 1.7 版本正式納入了標準庫。
Context,也叫上下文,它的接口定義如下
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
可以看到 Context 接口共有 4 個方法
Deadline
:返回的第一個值是 截止時間,到了這個時間點,Context 會自動觸發 Cancel 動作。返回的第二個值是 一個布爾值,true 表示設置了截止時間,false 表示沒有設置截止時間,如果沒有設置截止時間,就要手動調用 cancel 函數取消 Context。Done
:返回一個只讀的通道(只有在被cancel后才會返回),類型為struct{}
。當這個通道可讀時,意味著parent context已經發起了取消請求,根據這個信號,開發者就可以做一些清理動作,退出goroutine。Err
:返回 context 被 cancel 的原因。Value
:返回被綁定到 Context 的值,是一個鍵值對,所以要通過一個Key才可以獲取對應的值,這個值一般是線程安全的。
2. 為何需要 Context?
當一個協程(goroutine)開啟后,我們是無法強制關閉它的。
常見的關閉協程的原因有如下幾種:
- goroutine 自己跑完結束退出
- 主進程crash退出,goroutine 被迫退出
- 通過通道發送信號,引導協程的關閉。
第一種,屬于正常關閉,不在今天討論范圍之內。
第二種,屬于異常關閉,應當優化代碼。
第三種,才是開發者可以手動控制協程的方法,代碼示例如下:
func main() { stop := make(chan bool) go func() { for { select { case <-stop: fmt.Println("監控退出,停止了...") return default: fmt.Println("goroutine監控中...") time.Sleep(2 * time.Second) } } }() time.Sleep(10 * time.Second) fmt.Println("可以了,通知監控停止") stop<- true //為了檢測監控過是否停止,如果沒有監控輸出,就表示停止了 time.Sleep(5 * time.Second) }
例子中我們定義一個stop
的chan,通知他結束后臺goroutine。實現也非常簡單,在后臺goroutine中,使用select判斷stop
是否可以接收到值,如果可以接收到,就表示可以退出停止了;如果沒有接收到,就會執行default
里的監控邏輯,繼續監控,只到收到stop
的通知。
以上是一個 goroutine 的場景,如果是多個 goroutine ,每個goroutine 底下又開啟了多個 goroutine 的場景呢?在 飛雪無情的博客 里關于為何要使用 Context,他是這么說的
chan+select的方式,是比較優雅的結束一個goroutine的方式,不過這種方式也有局限性,如果有很多goroutine都需要控制結束怎么辦呢?如果這些goroutine又衍生了其他