golang學習筆記之-context詳細理解篇

image.png
- context.Background():可以簡單理解我們知道這個上下文要去幹什麼
- context.TODO():可以簡單理解我們不清楚要使用哪個上下文、或者還沒有可用的上下文
下面程式碼演示:
1.context.WithCancel():返回Context和取消函式用來取消Context
package main import ( "context" "log" "os" "time" ) var ( logg *log.Logger ) func work(ctx context.Context, ch chan bool) { for { select { case <-ctx.Done(): logg.Println(`下班!`) ch <- true return default: logg.Println(`上班!`) time.Sleep(2 * time.Second) } } } func main() { ch := make(chan bool) logg = log.New(os.Stdout, "", log.Ltime) ctx, cancel := context.WithCancel(context.Background()) go work(ctx, ch) time.Sleep(10 * time.Second) //取消函式:當cancel被呼叫時,WithCancel遍歷Done以執行關閉; cancel() // 這個chan是為了保證子的goroutine執行完,當然也可以不用chan用time.Sleep停止幾秒 <-ch logg.Println(`無腦發呆中!`) } /* outfile: 17:27:52 上班! 17:27:54 上班! 17:27:56 上班! 17:27:58 上班! 17:28:00 上班! 17:28:02 下班! 17:28:02 無腦發呆中! */
2.context.WithDeadline()和context.WithTimeout():返回Context和取消函式用來取消Context(這個取消函式會根據設定的時間自動取消),
package main import ( "context" "log" "os" "time" ) var ( logg *log.Logger ) func work(ctx context.Context, ch chan bool) { for { select { case <-ctx.Done(): logg.Println(`下班!`) ch <- true return default: logg.Println(`上班!`) time.Sleep(2 * time.Second) } } } func main() { ch := make(chan bool) logg = log.New(os.Stdout, "", log.Ltime) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) go work(ctx, ch) time.Sleep(10 * time.Second) //取消函式:當cancel被呼叫時,context.WithDeadline設定的時間超過了,關閉ctx.Done通道。 cancel() // 這個chan是為了保證子的goroutine執行完,當然也可以不用chan用time.Sleep停止幾秒 <-ch logg.Println(`無腦發呆中!`) } /* outfile: 17:29:43 上班! 17:29:45 上班! 17:29:47 上班! 17:29:49 下班! 17:29:53 無腦發呆中! */
3.context.WithTimeout
package main import ( "context" "log" "os" "time" ) var ( logg *log.Logger ) func work(ctx context.Context, ch chan bool) { for { select { case <-ctx.Done(): logg.Println(`下班!`) ch <- true return default: logg.Println(`上班!`) time.Sleep(2 * time.Second) } } } func main() { ch := make(chan bool) logg = log.New(os.Stdout, "", log.Ltime) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) go work(ctx, ch) time.Sleep(10 * time.Second) //取消函式:當cancel被呼叫時,context.WithTimeout設定的時間超過後,關閉ctx.Done通道; cancel() // 這個chan是為了保證子的goroutine執行完,當然也可以不用chan用time.Sleep停止幾秒 <-ch logg.Println(`無腦發呆中!`) } /*outfile: 17:34:56 上班! 17:34:58 上班! 17:35:00 上班! 17:35:02 下班! 17:35:06 無腦發呆中! */
- context.WithCancel():執行取消函式就取消
- context.WithDeadline、context.WithTimeout:超時的時候就取消
4.Deadline獲取超時時間
package main import ( "context" "fmt" "log" "os" "time" ) var ( logg *log.Logger ) func work(ctx context.Context, ch chan bool) { for { /* Deadline:是獲取設定的超時時間: 第一個返回值:設定的超時時間,到超時時間Context會自動發起取消請求 第二個返回值ok==false時表示沒有設定截止時間,如果需要取消的話需要呼叫取消函式進行取消。 */ if deadline, ok := ctx.Deadline(); ok { fmt.Println(deadline) if time.Now().After(deadline) { logg.Println(`超時退出!`) //這裡是為了演示,Context中的Err()輸出:context deadline exceeded logg.Printf(ctx.Err().Error()) ch <- true return } } select { case <-ctx.Done(): logg.Println(`下班!`) ch <- true return default: logg.Println(`上班!`) time.Sleep(1 * time.Second) } } } func main() { ch := make(chan bool) logg = log.New(os.Stdout, "", log.Ltime) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) go work(ctx, ch) time.Sleep(10 * time.Second) //取消函式:當cancel被呼叫時,context.WithTimeout設定的時間超過後,關閉ctx.Done通道; cancel() // 這個chan是為了保證子的goroutine執行完,當然也可以不用chan用time.Sleep停止幾秒 <-ch logg.Println(`無腦發呆中!`) } /* outfile: 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:42 上班! 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:43 上班! 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:44 上班! 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:45 上班! 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:46 上班! 2019-01-30 18:23:47.851042 +0800 CST m=+5.000360703 18:23:47 超時退出! 18:23:47 context deadline exceeded //這裡就是ctx超時的時候ctx.Err的錯誤訊息。 18:23:52 無腦發呆中! */
5.遇到某個上下文在傳遞的時候:context.WithCancel()儲存和檢索附加於請求的資料包.當一個函式建立一個goroutine和Context時,它通常會啟動一個為請求提供服務的程序,並且子函式可能需要相關的請求資訊。
package main import ( "context" "fmt" ) func main() { ProcessRequest("admin", "admin888") } func ProcessRequest(UserName, PassWord string) { ctx := context.WithValue(context.Background(), "UserName", UserName) ctx = context.WithValue(ctx, "PassWord", PassWord) HandleResponse(ctx) } func HandleResponse(ctx context.Context) { fmt.Printf("處理響應 使用者名稱:%v 密碼:%v", ctx.Value("UserName"), ctx.Value("PassWord"), ) } /* outfile: 處理響應 使用者名稱:admin 密碼:admin888 */
- 很簡單的用法,不過也是有限制的:
使用的key必須是可比較的,也就是說== 和 != 必須能返回正確的結果
返回值必須是併發安全的,這樣才能從多個goroutine訪問
- 由於Context的Value(key interface{}) interface{} 鍵和值都被定義為interface{},當試圖檢索值時,會失去其型別安全性。基於此,Go建議在context中儲存和檢索值時遵循一些規則。
- 推薦在包中自行定義key的型別,這樣無論是否其他包執行相同的操作都可以防止context中的衝突。看下面這個例子:
type foo int type bar int m := make(map[interface{}]int) m[foo(1)] = 1 m[bar(1)] = 2 fmt.Printf("%v", m) /* map[1:2 1:1] */
- 可以看到,雖然基礎值是相同的,但不同型別的資訊會在map中區分它們。由於你為包定義的key型別未匯出,因此其他包不會與你在包中生成的key衝突。
- 由於用於儲存資料的key是非匯出的,因此我們必須匯出執行檢索資料的函式。這很容易做到,因為它允許這些資料的使用者使用靜態的,型別安全的函式。
當你把所有這些放在一起時,你會得到類似下面的例子:
package main import ( "context" "fmt" ) func main() { ProcessRequest("jane", "abc123") } type ctxKey int const ( ctxUserName ctxKey = iota ctxPassWord ) func UserName(c context.Context) string { return c.Value(ctxUserName).(string) } func PassWord(c context.Context) string { return c.Value(ctxPassWord).(string) } func ProcessRequest(UserName, PassWord string) { ctx := context.WithValue(context.Background(), ctxUserName, UserName) ctx = context.WithValue(ctx, ctxPassWord, PassWord) HandleResponse(ctx) } func HandleResponse(ctx context.Context) { fmt.Printf( "處理響應 使用者名稱:%v 密碼:%v", UserName(ctx), PassWord(ctx), ) } /*outfile: 處理響應 使用者名稱:jane 密碼:abc123 */