自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一起聊聊 Go Context 的正確使用姿勢

開發(fā) 后端
在 Go 語言中,Goroutine(協(xié)程),也就是關(guān)鍵字 go 是一個家喻戶曉的高級用法。這起的非常妙,說到 Go,就會想到這一門語言,想到 goroutine 這一關(guān)鍵字,而與之關(guān)聯(lián)最深的就是 context。

[[410737]]

本文轉(zhuǎn)載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉(zhuǎn)載本文請聯(lián)系腦子進煎魚了公眾號。

大家好,我是煎魚。

在 Go 語言中,Goroutine(協(xié)程),也就是關(guān)鍵字 go 是一個家喻戶曉的高級用法。這起的非常妙,說到 Go,就會想到這一門語言,想到 goroutine 這一關(guān)鍵字,而與之關(guān)聯(lián)最深的就是 context。

背景

平時在 Go 工程中開發(fā)中,幾乎所有服務(wù)端(例如:HTTP Server)的默認實現(xiàn),都在處理請求時新起 goroutine 進行處理。

但一開始存在一個問題,那就是當一個請求被取消或超時時,所有在該請求上工作的 goroutines 應(yīng)該迅速退出,以便系統(tǒng)可以回收他們正在使用的任何資源。

當年可沒有 context 標準庫。很折騰。因此 Go 官方在 2014 年正式宣發(fā)了 context 標準庫,形成一個完整的閉環(huán)。

但有了 context 標準庫,Go 愛好者們又奇怪了,前段時間我就被問到了:“Go context 的正確使用姿勢是怎么樣的”?

(一張忘記在哪里被問的隱形截圖)

今天這篇文章就由煎魚帶你看看。

Context 用法

在 Go context 用法中,我們常常將其與 select 關(guān)鍵字結(jié)合使用,用于監(jiān)聽其是否結(jié)束、取消等。

代碼如下:

  1. const shortDuration = 1 * time.Millisecond 
  2.  
  3. func main() { 
  4.  ctx, cancel := context.WithTimeout(context.Background(), shortDuration) 
  5.  defer cancel() 
  6.  
  7.  select { 
  8.  case <-time.After(1 * time.Second): 
  9.   fmt.Println("腦子進煎魚了"
  10.  case <-ctx.Done(): 
  11.   fmt.Println(ctx.Err()) 
  12.  } 

輸出結(jié)果:

  1. context deadline exceeded 

如果是更進一步結(jié)合 goroutine 的話,常見的例子是:

  1. func(ctx context.Context) <-chan int { 
  2.  dst := make(chan int
  3.  n := 1 
  4.  go func() { 
  5.   for { 
  6.    select { 
  7.    case <-ctx.Done(): 
  8.     return 
  9.    case dst <- n: 
  10.     n++ 
  11.    } 
  12.   } 
  13.  }() 
  14.  return dst 

我們平時工程中會起很多的 goroutine,這時候會在 goroutine 內(nèi)結(jié)合 for+select,針對 context 的事件進行處理,達到跨 goroutine 控制的目的。

正確的使用姿勢

對第三方調(diào)用要傳入 context

在 Go 語言中,Context 的默認支持已經(jīng)是約定俗稱的規(guī)范了。因此在我們對第三方有調(diào)用訴求的時候,要傳入 context:

  1. func main() { 
  2.  req, err := http.NewRequest("GET""https://eddycjy.com/", nil) 
  3.  if err != nil { 
  4.   fmt.Printf("http.NewRequest err: %+v", err) 
  5.   return 
  6.  } 
  7.  
  8.  ctx, cancel := context.WithTimeout(req.Context(), 50*time.Millisecond) 
  9.  defer cancel() 
  10.  
  11.  req = req.WithContext(ctx) 
  12.  resp, err := http.DefaultClient.Do(req) 
  13.  if err != nil { 
  14.   fmt.Printf("http.DefaultClient.Do err: %+v", err) 
  15.   return 
  16.  } 
  17.  defer resp.Body.Close() 

這樣子由于第三方開源庫已經(jīng)實現(xiàn)了根據(jù) context 的超時控制,那么當你所傳入的時間到達時,將會中斷調(diào)用。

若你發(fā)現(xiàn)第三方開源庫沒支持 context,那建議趕緊跑,換一個。免得在微服務(wù)體系下出現(xiàn)級聯(lián)故障,還沒有簡單的手段控制,那就很麻煩了。

不要將上下文存儲在結(jié)構(gòu)類型中

大家會發(fā)現(xiàn),在 Go 語言中,所有的第三方開源庫,業(yè)務(wù)代碼。清一色的都會將 context 放在方法的一個入?yún)?shù),作為首位形參。

例如:

標準要求:每個方法的第一個參數(shù)都將 context 作為第一個參數(shù),并使用 ctx 變量名慣用語。

當然,我們也不能一桿子打死所有情況。確實存在極少數(shù)是把 context 放在結(jié)構(gòu)體中的。基本常見于:

  • 底層基礎(chǔ)庫。
  • DDD 結(jié)構(gòu)。

每個請求都是獨立的,context 自然每個都不一樣,想清楚自己的應(yīng)用使用場景很重要,否則遵循 Go 基本規(guī)范就好。

在真實案例來看,有的 Leader 會單純?yōu)榱瞬幌腩l繁傳 context 而設(shè)計成結(jié)構(gòu)體,結(jié)果導(dǎo)致一線 RD 就得天天 NewXXX,甚至有時候忘記了,還得背個小鍋。

函數(shù)調(diào)用鏈必須傳播上下文

我們會把 context 作為方法首位,本質(zhì)目的是為了傳播 context,自行完整調(diào)用鏈路上的各類控制:

  1. func List(ctx context.Context, db *sqlx.DB) ([]User, error) { 
  2.  ctx, span := trace.StartSpan(ctx, "internal.user.List"
  3.  defer span.End() 
  4.  
  5.  users := []User{} 
  6.  const q = `SELECT * FROM users` 
  7.  
  8.  if err := db.SelectContext(ctx, &users, q); err != nil { 
  9.   return nil, errors.Wrap(err, "selecting users"
  10.  } 
  11.  
  12.  return users, nil 

像在上述例子中,我們會把所傳入方法的 context 一層層的傳進去下一級方法。這里就是將外部的 context 傳入 List 方法,再傳入 SQL 執(zhí)行的方法,解決了 SQL 執(zhí)行語句的時間問題。

context 的繼承和派生

在 Go 標準庫 context 中具有以下派生 context 的標準方法:

  1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 
  2. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 
  3. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 

代碼例子如下:

  1. func handle(w http.ResponseWriter, req *http.Request) { 
  2.   // parent context 
  3.  timeout, _ := time.ParseDuration(req.FormValue("timeout")) 
  4.  ctx, cancel := context.WithTimeout(context.Background(), timeout) 
  5.  
  6.   // chidren context 
  7.  newCtx, cancel := context.WithCancel(ctx) 
  8.  defer cancel() 
  9.  // do something... 

一般會有父級 context 和子級 context 的區(qū)別,我們要保證在程序的行為中上下文對于多個 goroutine 同時使用是安全的。并且存在父子級別關(guān)系,父級 context 關(guān)閉或超時,可以繼而影響到子級 context 的程序。

不傳遞 nil context

很多時候我們在創(chuàng)建 context 時,還不知道其具體的作用和下一步用途是什么。

這種時候大家可能會直接使用 context.Background 方法:

  1. var ( 
  2.    background = new(emptyCtx) 
  3.    todo       = new(emptyCtx) 
  4.  
  5. func Background() Context { 
  6.    return background 
  7.  
  8. func TODO() Context { 
  9.    return todo 

但在實際的 context 建議中,我們會建議使用 context.TODO 方法來創(chuàng)建頂級的 context,直到弄清楚實際 Context 的下一步用途,再進行變更。

context 僅傳遞必要的值

我們在使用 context 作為上下文時,經(jīng)常有信息傳遞的訴求。像是在 gRPC 中就會有 metadata 的概念,而在 gin 中就會自己封裝 context 作為參數(shù)管理。

Go 標準庫 context 也有提供相關(guān)的方法:

  1. type Context 
  2.     func WithValue(parent Context, key, val interface{}) Context 

代碼例子如下:

  1. func main() { 
  2.  type favContextKey string 
  3.  f := func(ctx context.Context, k favContextKey) { 
  4.   if v := ctx.Value(k); v != nil { 
  5.    fmt.Println("found value:", v) 
  6.    return 
  7.   } 
  8.   fmt.Println("key not found:", k) 
  9.  } 
  10.  
  11.  k := favContextKey("腦子進"
  12.  ctx := context.WithValue(context.Background(), k, "煎魚"
  13.  
  14.  f(ctx, k) 
  15.  f(ctx, favContextKey("小咸魚")) 

輸出結(jié)果:

  1. found value: 煎魚 
  2. key not found: 小咸魚 

在規(guī)范中,我們建議 context 在傳遞時,僅攜帶必要的參數(shù)給予其他的方法,或是 goroutine。甚至在 gRPC 中會做嚴格的出、入上下文參數(shù)的控制。

在業(yè)務(wù)場景上,context 傳值適用于傳必要的業(yè)務(wù)核心屬性,例如:租戶號、小程序ID 等。不要將可選參數(shù)放到 context 中,否則可能會一團糟。

總結(jié)

 

  • 對第三方調(diào)用要傳入 context,用于控制遠程調(diào)用。
  • 不要將上下文存儲在結(jié)構(gòu)類型中,盡可能的作為函數(shù)第一位形參傳入。
  • 函數(shù)調(diào)用鏈必須傳播上下文,實現(xiàn)完整鏈路上的控制。
  • context 的繼承和派生,保證父、子級 context 的聯(lián)動。
  • 不傳遞 nil context,不確定的 context 應(yīng)當使用 TODO。
  • context 僅傳遞必要的值,不要讓可選參數(shù)揉在一起。

 

責(zé)任編輯:武曉燕 來源: 腦子進煎魚了
相關(guān)推薦

2024-02-26 00:00:00

Go性能工具

2023-03-26 23:47:32

Go內(nèi)存模型

2024-06-27 08:54:22

Go模塊團隊

2024-05-17 08:47:33

數(shù)組切片元素

2021-09-11 19:02:34

Hook使用版本

2023-04-26 07:30:00

promptUI非結(jié)構(gòu)化

2022-10-08 00:00:05

SQL機制結(jié)構(gòu)

2022-12-07 09:01:14

布局容器VStack?

2021-08-27 07:06:10

IOJava抽象

2024-02-20 21:34:16

循環(huán)GolangGo

2023-08-04 08:20:56

DockerfileDocker工具

2022-05-24 08:21:16

數(shù)據(jù)安全API

2023-08-10 08:28:46

網(wǎng)絡(luò)編程通信

2023-09-10 21:42:31

2023-06-30 08:18:51

敏捷開發(fā)模式

2023-09-21 08:16:56

JDK 21向量計算計算

2024-07-18 08:54:49

2022-06-17 08:17:53

配置profile技巧

2023-03-07 07:05:29

生產(chǎn)數(shù)據(jù)庫運維
點贊
收藏

51CTO技術(shù)棧公眾號