精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

小白也能看懂的 Context 包詳解:從入門到精通

開發(fā) 前端
context被當作第一個參數(shù)(官方建議),并且不斷透傳下去,基本一個項目代碼中到處都是context,但是你們真的知道它有何作用嗎以及它是如何起作用的嗎?

[[432383]]

前言

哈嘍,大家好,我是asong。今天想與大家分享context包,經過一年的沉淀,重新出發(fā),基于Go1.17.1從源碼角度再次分析,不過這次不同的是,我打算先從入門開始,因為大多數(shù)初學的讀者都想先知道怎么用,然后才會關心源碼是如何實現(xiàn)的。

相信大家在日常工作開發(fā)中一定會看到這樣的代碼:

  1. func a1(ctx context ...){ 
  2.   b1(ctx) 
  3. func b1(ctx context ...){ 
  4.   c1(ctx) 
  5. func c1(ctx context ...) 

context被當作第一個參數(shù)(官方建議),并且不斷透傳下去,基本一個項目代碼中到處都是context,但是你們真的知道它有何作用嗎以及它是如何起作用的嗎?我記得我第一次接觸context時,同事都說這個用來做并發(fā)控制的,可以設置超時時間,超時就會取消往下執(zhí)行,快速返回,我就單純的認為只要函數(shù)中帶著context參數(shù)往下傳遞就可以做到超時取消,快速返回。相信大多數(shù)初學者也都是和我一個想法,其實這是一個錯誤的思想,其取消機制采用的也是通知機制,單純的透傳并不會起作用,比如你這樣寫代碼:

  1. func main()  { 
  2.  ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second
  3.  defer cancel() 
  4.  go Monitor(ctx) 
  5.  
  6.  time.Sleep(20 * time.Second
  7.  
  8. func Monitor(ctx context.Context)  { 
  9.  for { 
  10.   fmt.Print("monitor"
  11.  } 

即使context透傳下去了,沒有監(jiān)聽取消信號也是不起任何作用的。所以了解context的使用還是很有必要的,本文就先從使用開始,逐步解析Go語言的context包,下面我們就開始嘍!!!

context包的起源與作用

看官方博客我們可以知道context包是在go1.7版本中引入到標準庫中的:

context可以用來在goroutine之間傳遞上下文信息,相同的context可以傳遞給運行在不同goroutine中的函數(shù),上下文對于多個goroutine同時使用是安全的,context包定義了上下文類型,可以使用background、TODO創(chuàng)建一個上下文,在函數(shù)調用鏈之間傳播context,也可以使用WithDeadline、WithTimeout、WithCancel 或 WithValue 創(chuàng)建的修改副本替換它,聽起來有點繞,其實總結起就是一句話:context的作用就是在不同的goroutine之間同步請求特定的數(shù)據(jù)、取消信號以及處理請求的截止日期。

目前我們常用的一些庫都是支持context的,例如gin、database/sql等庫都是支持context的,這樣更方便我們做并發(fā)控制了,只要在服務器入口創(chuàng)建一個context上下文,不斷透傳下去即可。

context的使用

創(chuàng)建context

context包主要提供了兩種方式創(chuàng)建context:

  • context.Backgroud()
  • context.TODO()

這兩個函數(shù)其實只是互為別名,沒有差別,官方給的定義是:

  • context.Background 是上下文的默認值,所有其他的上下文都應該從它衍生(Derived)出來。
  • context.TODO 應該只在不確定應該使用哪種上下文時使用;

所以在大多數(shù)情況下,我們都使用context.Background作為起始的上下文向下傳遞。

上面的兩種方式是創(chuàng)建根context,不具備任何功能,具體實踐還是要依靠context包提供的With系列函數(shù)來進行派生:

  1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 
  2. func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 
  3. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 
  4. func WithValue(parent Context, key, val interface{}) Context 

這四個函數(shù)都要基于父Context衍生,通過這些函數(shù),就創(chuàng)建了一顆Context樹,樹的每個節(jié)點都可以有任意多個子節(jié)點,節(jié)點層級可以有任意多個,畫個圖表示一下:

基于一個父Context可以隨意衍生,其實這就是一個Context樹,樹的每個節(jié)點都可以有任意多個子節(jié)點,節(jié)點層級可以有任意多個,每個子節(jié)點都依賴于其父節(jié)點,例如上圖,我們可以基于Context.Background衍生出四個子context:ctx1.0-cancel、ctx2.0-deadline、ctx3.0-timeout、ctx4.0-withvalue,這四個子context還可以作為父context繼續(xù)向下衍生,即使其中ctx1.0-cancel 節(jié)點取消了,也不影響其他三個父節(jié)點分支。

創(chuàng)建context方法和context的衍生方法就這些,下面我們就一個一個來看一下他們如何被使用。

WithValue攜帶數(shù)據(jù)

我們日常在業(yè)務開發(fā)中都希望能有一個trace_id能串聯(lián)所有的日志,這就需要我們打印日志時能夠獲取到這個trace_id,在python中我們可以用gevent.local來傳遞,在java中我們可以用ThreadLocal來傳遞,在Go語言中我們就可以使用Context來傳遞,通過使用WithValue來創(chuàng)建一個攜帶trace_id的context,然后不斷透傳下去,打印日志時輸出即可,來看使用例子:

  1. const ( 
  2.  KEY = "trace_id" 
  3.  
  4. func NewRequestID() string { 
  5.  return strings.Replace(uuid.New().String(), "-""", -1) 
  6.  
  7. func NewContextWithTraceID() context.Context { 
  8.  ctx := context.WithValue(context.Background(), KEY,NewRequestID()) 
  9.  return ctx 
  10.  
  11. func PrintLog(ctx context.Context, message string)  { 
  12.  fmt.Printf("%s|info|trace_id=%s|%s",time.Now().Format("2006-01-02 15:04:05") , GetContextValue(ctx, KEY), message) 
  13.  
  14. func GetContextValue(ctx context.Context,k string)  string{ 
  15.  v, ok := ctx.Value(k).(string) 
  16.  if !ok{ 
  17.   return "" 
  18.  } 
  19.  return v 
  20.  
  21. func ProcessEnter(ctx context.Context) { 
  22.  PrintLog(ctx, "Golang夢工廠"
  23.  
  24.  
  25. func main()  { 
  26.  ProcessEnter(NewContextWithTraceID()) 

輸出結果:

  1. 2021-10-31 15:13:25|info|trace_id=7572e295351e478e91b1ba0fc37886c0|Golang夢工廠 
  2. Process finished with the exit code 0 

我們基于context.Background創(chuàng)建一個攜帶trace_id的ctx,然后通過context樹一起傳遞,從中派生的任何context都會獲取此值,我們最后打印日志的時候就可以從ctx中取值輸出到日志中。目前一些RPC框架都是支持了Context,所以trace_id的向下傳遞就更方便了。

在使用withVaule時要注意四個事項:

  • 不建議使用context值傳遞關鍵參數(shù),關鍵參數(shù)應該顯示的聲明出來,不應該隱式處理,context中最好是攜帶簽名、trace_id這類值。
  • 因為攜帶value也是key、value的形式,為了避免context因多個包同時使用context而帶來沖突,key建議采用內置類型。
  • 上面的例子我們獲取trace_id是直接從當前ctx獲取的,實際我們也可以獲取父context中的value,在獲取鍵值對是,我們先從當前context中查找,沒有找到會在從父context中查找該鍵對應的值直到在某個父context中返回 nil 或者查找到對應的值。
  • context傳遞的數(shù)據(jù)中key、value都是interface類型,這種類型編譯期無法確定類型,所以不是很安全,所以在類型斷言時別忘了保證程序的健壯性。

超時控制

通常健壯的程序都是要設置超時時間的,避免因為服務端長時間響應消耗資源,所以一些web框架或rpc框架都會采用withTimeout或者withDeadline來做超時控制,當一次請求到達我們設置的超時時間,就會及時取消,不在往下執(zhí)行。withTimeout和withDeadline作用是一樣的,就是傳遞的時間參數(shù)不同而已,他們都會通過傳入的時間來自動取消Context,這里要注意的是他們都會返回一個cancelFunc方法,通過調用這個方法可以達到提前進行取消,不過在使用的過程還是建議在自動取消后也調用cancelFunc去停止定時減少不必要的資源浪費。

withTimeout、WithDeadline不同在于WithTimeout將持續(xù)時間作為參數(shù)輸入而不是時間對象,這兩個方法使用哪個都是一樣的,看業(yè)務場景和個人習慣了,因為本質withTimout內部也是調用的WithDeadline。

現(xiàn)在我們就舉個例子來試用一下超時控制,現(xiàn)在我們就模擬一個請求寫兩個例子:

  • 達到超時時間終止接下來的執(zhí)行
  1. func main()  { 
  2.  HttpHandler() 
  3.  
  4. func NewContextWithTimeout() (context.Context,context.CancelFunc) { 
  5.  return context.WithTimeout(context.Background(), 3 * time.Second
  6.  
  7. func HttpHandler()  { 
  8.  ctx, cancel := NewContextWithTimeout() 
  9.  defer cancel() 
  10.  deal(ctx) 
  11.  
  12. func deal(ctx context.Context)  { 
  13.  for i:=0; i< 10; i++ { 
  14.   time.Sleep(1*time.Second
  15.   select { 
  16.   case <- ctx.Done(): 
  17.    fmt.Println(ctx.Err()) 
  18.    return 
  19.   default
  20.    fmt.Printf("deal time is %d\n", i) 
  21.   } 
  22.  } 

輸出結果:

  1. deal time is 0 
  2. deal time is 1 
  3. context deadline exceeded 
  • 沒有達到超時時間終止接下來的執(zhí)行
  1. func main()  { 
  2.  HttpHandler1() 
  3.  
  4. func NewContextWithTimeout1() (context.Context,context.CancelFunc) { 
  5.  return context.WithTimeout(context.Background(), 3 * time.Second
  6.  
  7. func HttpHandler1()  { 
  8.  ctx, cancel := NewContextWithTimeout1() 
  9.  defer cancel() 
  10.  deal1(ctx, cancel) 
  11.  
  12. func deal1(ctx context.Context, cancel context.CancelFunc)  { 
  13.  for i:=0; i< 10; i++ { 
  14.   time.Sleep(1*time.Second
  15.   select { 
  16.   case <- ctx.Done(): 
  17.    fmt.Println(ctx.Err()) 
  18.    return 
  19.   default
  20.    fmt.Printf("deal time is %d\n", i) 
  21.    cancel() 
  22.   } 
  23.  } 

輸出結果:

  1. deal time is 0 
  2. context canceled 

使用起來還是比較容易的,既可以超時自動取消,又可以手動控制取消。這里大家要記的一個坑,就是我們往從請求入口透傳的調用鏈路中的context是攜帶超時時間的,如果我們想在其中單獨開一個goroutine去處理其他的事情并且不會隨著請求結束后而被取消的話,那么傳遞的context要基于context.Background或者context.TODO重新衍生一個傳遞,否決就會和預期不符合了,可以看一下我之前的一篇踩坑文章:context使用不當引發(fā)的一個bug。

withCancel取消控制

日常業(yè)務開發(fā)中我們往往為了完成一個復雜的需求會開多個gouroutine去做一些事情,這就導致我們會在一次請求中開了多個goroutine確無法控制他們,這時我們就可以使用withCancel來衍生一個context傳遞到不同的goroutine中,當我想讓這些goroutine停止運行,就可以調用cancel來進行取消。

來看一個例子:

  1. func main()  { 
  2.  ctx,cancel := context.WithCancel(context.Background()) 
  3.  go Speak(ctx) 
  4.  time.Sleep(10*time.Second
  5.  cancel() 
  6.  time.Sleep(1*time.Second
  7.  
  8. func Speak(ctx context.Context)  { 
  9.  for range time.Tick(time.Second){ 
  10.   select { 
  11.   case <- ctx.Done(): 
  12.    fmt.Println("我要閉嘴了"
  13.    return 
  14.   default
  15.    fmt.Println("balabalabalabala"
  16.   } 
  17.  } 

運行結果:

  1. balabalabalabala 
  2. ....省略 
  3. balabalabalabala 
  4. 我要閉嘴了 

我們使用withCancel創(chuàng)建一個基于Background的ctx,然后啟動一個講話程序,每隔1s說一話,main函數(shù)在10s后執(zhí)行cancel,那么speak檢測到取消信號就會退出。

自定義Context

因為Context本質是一個接口,所以我們可以通過實現(xiàn)Context達到自定義Context的目的,一般在實現(xiàn)Web框架或RPC框架往往采用這種形式,比如gin框架的Context就是自己有封裝了一層,具體代碼和實現(xiàn)就貼在這里,有興趣可以看一下gin.Context是如何實現(xiàn)的。

源碼賞析

Context其實就是一個接口,定義了四個方法:

  1. type Context interface { 
  2.  Deadline() (deadline time.Time, ok bool) 
  3.  Done() <-chan struct{} 
  4.  Err() error 
  5.  Value(key interface{}) interface{} 
  • Deadlne方法:當Context自動取消或者到了取消時間被取消后返回
  • Done方法:當Context被取消或者到了deadline返回一個被關閉的channel
  • Err方法:當Context被取消或者關閉后,返回context取消的原因
  • Value方法:獲取設置的key對應的值

這個接口主要被三個類繼承實現(xiàn),分別是emptyCtx、ValueCtx、cancelCtx,采用匿名接口的寫法,這樣可以對任意實現(xiàn)了該接口的類型進行重寫。

下面我們就從創(chuàng)建到使用來層層分析。

創(chuàng)建根Context

其在我們調用context.Background、context.TODO時創(chuàng)建的對象就是empty:

  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 

Background和TODO還是一模一樣的,官方說:background它通常由主函數(shù)、初始化和測試使用,并作為傳入請求的頂級上下文;TODO是當不清楚要使用哪個 Context 或尚不可用時,代碼應使用 context.TODO,后續(xù)在在進行替換掉,歸根結底就是語義不同而已。

emptyCtx類

emptyCtx主要是給我們創(chuàng)建根Context時使用的,其實現(xiàn)方法也是一個空結構,實際源代碼長這樣:

  1. type emptyCtx int 
  2.  
  3. func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { 
  4.  return 
  5.  
  6. func (*emptyCtx) Done() <-chan struct{} { 
  7.  return nil 
  8.  
  9. func (*emptyCtx) Err() error { 
  10.  return nil 
  11.  
  12. func (*emptyCtx) Value(key interface{}) interface{} { 
  13.  return nil 
  14.  
  15. func (e *emptyCtx) String() string { 
  16.  switch e { 
  17.  case background: 
  18.   return "context.Background" 
  19.  case todo: 
  20.   return "context.TODO" 
  21.  } 
  22.  return "unknown empty Context" 

WithValue的實現(xiàn)

withValue內部主要就是調用valueCtx類:

  1. func WithValue(parent Context, key, val interface{}) Context { 
  2.  if parent == nil { 
  3.   panic("cannot create context from nil parent"
  4.  } 
  5.  if key == nil { 
  6.   panic("nil key"
  7.  } 
  8.  if !reflectlite.TypeOf(key).Comparable() { 
  9.   panic("key is not comparable"
  10.  } 
  11.  return &valueCtx{parent, key, val} 

valueCtx類

valueCtx目的就是為Context攜帶鍵值對,因為它采用匿名接口的繼承實現(xiàn)方式,他會繼承父Context,也就相當于嵌入Context當中了

  1. type valueCtx struct { 
  2.  Context 
  3.  key, val interface{} 

實現(xiàn)了String方法輸出Context和攜帶的鍵值對信息:

  1. func (c *valueCtx) String() string { 
  2.  return contextName(c.Context) + ".WithValue(type " + 
  3.   reflectlite.TypeOf(c.key).String() + 
  4.   ", val " + stringify(c.val) + ")" 

實現(xiàn)Value方法來存儲鍵值對:

  1. func (c *valueCtx) Value(key interface{}) interface{} { 
  2.  if c.key == key { 
  3.   return c.val 
  4.  } 
  5.  return c.Context.Value(key

看圖來理解一下:

所以我們在調用Context中的Value方法時會層層向上調用直到最終的根節(jié)點,中間要是找到了key就會返回,否會就會找到最終的emptyCtx返回nil。

WithCancel的實現(xiàn)

我們來看一下WithCancel的入口函數(shù)源代碼:

  1. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 
  2.  if parent == nil { 
  3.   panic("cannot create context from nil parent"
  4.  } 
  5.  c := newCancelCtx(parent) 
  6.  propagateCancel(parent, &c) 
  7.  return &c, func() { c.cancel(true, Canceled) } 

這個函數(shù)執(zhí)行步驟如下:

  • 創(chuàng)建一個cancelCtx對象,作為子context
  • 然后調用propagateCancel構建父子context之間的關聯(lián)關系,這樣當父context被取消時,子context也會被取消。
  • 返回子context對象和子樹取消函數(shù)

我們先分析一下cancelCtx這個類。

cancelCtx類

cancelCtx繼承了Context,也實現(xiàn)了接口canceler:

  1. type cancelCtx struct { 
  2.  Context 
  3.  
  4.  mu       sync.Mutex            // protects following fields 
  5.  done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call 
  6.  children map[canceler]struct{} // set to nil by the first cancel call 
  7.  err      error                 // set to non-nil by the first cancel call 

字短解釋:

  • mu:就是一個互斥鎖,保證并發(fā)安全的,所以context是并發(fā)安全的
  • done:用來做context的取消通知信號,之前的版本使用的是chan struct{}類型,現(xiàn)在用atomic.Value做鎖優(yōu)化
  • children:key是接口類型canceler,目的就是存儲實現(xiàn)當前canceler接口的子節(jié)點,當根節(jié)點發(fā)生取消時,遍歷子節(jié)點發(fā)送取消信號
  • error:當context取消時存儲取消信息

這里實現(xiàn)了Done方法,返回的是一個只讀的channel,目的就是我們在外部可以通過這個阻塞的channel等待通知信號。

具體代碼就不貼了。我們先返回去看propagateCancel是如何做構建父子Context之間的關聯(lián)。

propagateCancel方法

代碼有點長,解釋有點麻煩,我把注釋添加到代碼中看起來比較直觀:

  1. func propagateCancel(parent Context, child canceler) { 
  2.   // 如果返回nil,說明當前父`context`從來不會被取消,是一個空節(jié)點,直接返回即可。 
  3.  done := parent.Done() 
  4.  if done == nil { 
  5.   return // parent is never canceled 
  6.  } 
  7.  
  8.   // 提前判斷一個父context是否被取消,如果取消了也不需要構建關聯(lián)了, 
  9.   // 把當前子節(jié)點取消掉并返回 
  10.  select { 
  11.  case <-done: 
  12.   // parent is already canceled 
  13.   child.cancel(false, parent.Err()) 
  14.   return 
  15.  default
  16.  } 
  17.  
  18.   // 這里目的就是找到可以“掛”、“取消”的context 
  19.  if p, ok := parentCancelCtx(parent); ok { 
  20.   p.mu.Lock() 
  21.     // 找到了可以“掛”、“取消”的context,但是已經被取消了,那么這個子節(jié)點也不需要 
  22.     // 繼續(xù)掛靠了,取消即可 
  23.   if p.err != nil { 
  24.    child.cancel(false, p.err) 
  25.   } else { 
  26.       // 將當前節(jié)點掛到父節(jié)點的childrn map中,外面調用cancel時可以層層取消 
  27.    if p.children == nil { 
  28.         // 這里因為childer節(jié)點也會變成父節(jié)點,所以需要初始化map結構 
  29.     p.children = make(map[canceler]struct{}) 
  30.    } 
  31.    p.children[child] = struct{}{} 
  32.   } 
  33.   p.mu.Unlock() 
  34.  } else { 
  35.     // 沒有找到可“掛”,“取消”的父節(jié)點掛載,那么就開一個goroutine 
  36.   atomic.AddInt32(&goroutines, +1) 
  37.   go func() { 
  38.    select { 
  39.    case <-parent.Done(): 
  40.     child.cancel(false, parent.Err()) 
  41.    case <-child.Done(): 
  42.    } 
  43.   }() 
  44.  } 

這段代碼真正產生疑惑的是這個if、else分支。不看代碼了,直接說為什么吧。因為我們可以自己定制context,把context塞進一個結構時,就會導致找不到可取消的父節(jié)點,只能重新起一個協(xié)程做監(jiān)聽。

對這塊有迷惑的推薦閱讀饒大大文章:[深度解密Go語言之context](https://www.cnblogs.com/qcrao-2018/p/11007503.html),定能為你排憂解惑。

cancel方法

最后我們再來看一下返回的cancel方法是如何實現(xiàn),這個方法會關閉上下文中的 Channel 并向所有的子上下文同步取消信號:

  1. func (c *cancelCtx) cancel(removeFromParent bool, err error) { 
  2.   // 取消時傳入的error信息不能為nil, context定義了默認error:var Canceled = errors.New("context canceled"
  3.  if err == nil { 
  4.   panic("context: internal error: missing cancel error"
  5.  } 
  6.   // 已經有錯誤信息了,說明當前節(jié)點已經被取消過了 
  7.  c.mu.Lock() 
  8.  if c.err != nil { 
  9.   c.mu.Unlock() 
  10.   return // already canceled 
  11.  } 
  12.    
  13.  c.err = err 
  14.   // 用來關閉channel,通知其他協(xié)程 
  15.  d, _ := c.done.Load().(chan struct{}) 
  16.  if d == nil { 
  17.   c.done.Store(closedchan) 
  18.  } else { 
  19.   close(d) 
  20.  } 
  21.   // 當前節(jié)點向下取消,遍歷它的所有子節(jié)點,然后取消 
  22.  for child := range c.children { 
  23.   // NOTE: acquiring the child's lock while holding parent's lock. 
  24.   child.cancel(false, err) 
  25.  } 
  26.   // 節(jié)點置空 
  27.  c.children = nil 
  28.  c.mu.Unlock() 
  29.   // 把當前節(jié)點從父節(jié)點中移除,只有在外部父節(jié)點調用時才會傳true 
  30.   // 其他都是傳false,內部調用都會因為c.children = nil被剔除出去 
  31.  if removeFromParent { 
  32.   removeChild(c.Context, c) 
  33.  } 

到這里整個WithCancel方法源碼就分析好了,通過源碼我們可以知道cancel方法可以被重復調用,是冪等的。

withDeadline、WithTimeout的實現(xiàn)

先看WithTimeout方法,它內部就是調用的WithDeadline方法:

  1. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 
  2.  return WithDeadline(parent, time.Now().Add(timeout)) 

所以我們重點來看withDeadline是如何實現(xiàn)的:

  1. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { 
  2.   // 不能為空`context`創(chuàng)建衍生context 
  3.  if parent == nil { 
  4.   panic("cannot create context from nil parent"
  5.  } 
  6.    
  7.   // 當父context的結束時間早于要設置的時間,則不需要再去單獨處理子節(jié)點的定時器了 
  8.  if cur, ok := parent.Deadline(); ok && cur.Before(d) { 
  9.   // The current deadline is already sooner than the new one. 
  10.   return WithCancel(parent) 
  11.  } 
  12.   // 創(chuàng)建一個timerCtx對象 
  13.  c := &timerCtx{ 
  14.   cancelCtx: newCancelCtx(parent), 
  15.   deadline:  d, 
  16.  } 
  17.   // 將當前節(jié)點掛到父節(jié)點上 
  18.  propagateCancel(parent, c) 
  19.    
  20.   // 獲取過期時間 
  21.  dur := time.Until(d) 
  22.   // 當前時間已經過期了則直接取消 
  23.  if dur <= 0 { 
  24.   c.cancel(true, DeadlineExceeded) // deadline has already passed 
  25.   return c, func() { c.cancel(false, Canceled) } 
  26.  } 
  27.  c.mu.Lock() 
  28.  defer c.mu.Unlock() 
  29.   // 如果沒被取消,則直接添加一個定時器,定時去取消 
  30.  if c.err == nil { 
  31.   c.timer = time.AfterFunc(dur, func() { 
  32.    c.cancel(true, DeadlineExceeded) 
  33.   }) 
  34.  } 
  35.  return c, func() { c.cancel(true, Canceled) } 

withDeadline相較于withCancel方法也就多了一個定時器去定時調用cancel方法,這個cancel方法在timerCtx類中進行了重寫,我們先來看一下timerCtx類,他是基于cancelCtx的,多了兩個字段:

  1. type timerCtx struct { 
  2.  cancelCtx 
  3.  timer *time.Timer // Under cancelCtx.mu. 
  4.  
  5.  deadline time.Time 

timerCtx實現(xiàn)的cancel方法,內部也是調用了cancelCtx的cancel方法取消:

  1. func (c *timerCtx) cancel(removeFromParent bool, err error) { 
  2.   // 調用cancelCtx的cancel方法取消掉子節(jié)點context 
  3.  c.cancelCtx.cancel(false, err) 
  4.   // 從父context移除放到了這里來做 
  5.  if removeFromParent { 
  6.   // Remove this timerCtx from its parent cancelCtx's children. 
  7.   removeChild(c.cancelCtx.Context, c) 
  8.  } 
  9.   // 停掉定時器,釋放資源 
  10.  c.mu.Lock() 
  11.  if c.timer != nil { 
  12.   c.timer.Stop() 
  13.   c.timer = nil 
  14.  } 
  15.  c.mu.Unlock() 

終于源碼部分我們就看完了,現(xiàn)在你何感想?

context的優(yōu)缺點

context包被設計出來就是做并發(fā)控制的,這個包有利有弊,個人總結了幾個優(yōu)缺點,歡迎評論區(qū)補充。

缺點

  • 影響代碼美觀,現(xiàn)在基本所有web框架、RPC框架都是實現(xiàn)了context,這就導致我們的代碼中每一個函數(shù)的一個參數(shù)都是context,即使不用也要帶著這個參數(shù)透傳下去,個人覺得有點丑陋。
  • context可以攜帶值,但是沒有任何限制,類型和大小都沒有限制,也就是沒有任何約束,這樣很容易導致濫用,程序的健壯很難保證;還有一個問題就是通過context攜帶值不如顯式傳值舒服,可讀性變差了。
  • 可以自定義context,這樣風險不可控,更加會導致濫用。
  • context取消和自動取消的錯誤返回不夠友好,無法自定義錯誤,出現(xiàn)難以排查的問題時不好排查。
  • 創(chuàng)建衍生節(jié)點實際是創(chuàng)建一個個鏈表節(jié)點,其時間復雜度為O(n),節(jié)點多了會掉支效率變低。

優(yōu)點

使用context可以更好的做并發(fā)控制,能更好的管理goroutine濫用。

context的攜帶者功能沒有任何限制,這樣我我們傳遞任何的數(shù)據(jù),可以說這是一把雙刃劍

網上都說context包解決了goroutine的cancelation問題,你覺得呢?

參考文章

https://pkg.go.dev/context@go1.7beta1#Background https://studygolang.com/articles/21531 https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/ https://www.cnblogs.com/qcrao-2018/p/11007503.html https://segmentfault.com/a/1190000039294140 https://www.flysnow.org/2017/05/12/go-in-action-go-context.html

總結

context雖然在使用上丑陋了一點,但是他卻能解決很多問題,日常業(yè)務開發(fā)中離不開context的使用,不過也別使用錯了context,其取消也采用的channel通知,所以代碼中還有要有監(jiān)聽代碼來監(jiān)聽取消信號,這點也是經常被廣大初學者容易忽視的一個點。

文中示例已上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/context_example

 

好啦,本文到這里就結束了,我是asong,我們下期見。

 

責任編輯:武曉燕 來源: Golang夢工廠
相關推薦

2017-02-22 15:04:52

2024-11-01 05:10:00

2019-12-27 09:47:05

大數(shù)據(jù)TomcatWeb

2020-02-15 17:16:05

Kubernetes容器

2019-11-18 10:38:03

線程池Java框架

2019-10-10 11:10:04

SpringBoot異步編程

2018-12-24 08:46:52

Kubernetes對象模型

2022-07-04 08:31:42

GitOpsGit基礎設施

2024-01-19 13:39:00

死鎖框架排查

2025-02-17 13:00:00

ChatGPT大模型AI

2025-02-17 10:09:54

2013-09-22 10:34:08

碼農機器學習算法

2019-03-26 11:15:34

AI機器學習人工智能

2024-12-18 18:53:48

2018-05-16 10:07:02

監(jiān)控報警系統(tǒng)

2020-03-31 10:36:07

數(shù)據(jù)平臺架構

2019-11-04 13:09:43

數(shù)據(jù)平臺架構

2017-11-02 12:08:56

2020-11-16 16:38:30

人工智能AI

2021-09-27 13:50:13

Python裝飾器函數(shù)
點贊
收藏

51CTO技術棧公眾號

久久久精品高清| 日产精品99久久久久久| 久久黄色一级视频| 2018av在线| 91视频观看免费| 国产欧美一区二区| xxxxxx国产| 99国内精品久久久久久久| 精品精品国产高清a毛片牛牛| 国产成人精品无码播放| av中文字幕在线观看| 91在线视频观看| 亚洲a成v人在线观看| 精品人妻一区二区色欲产成人| 国产精品久久久久久久久久10秀 | 中文字幕欧美一| 999国内精品视频在线| 少妇高潮av久久久久久| 欧美三区不卡| 三级精品视频久久久久| 中文字幕人妻一区二区三区| 欧美大片网站| 色综合天天视频在线观看 | 欧美一级二级三级区| av亚洲精华国产精华| 亚洲一区二区久久久久久久| 无码人妻丰满熟妇精品| 在线日韩av| 久久在线免费视频| 2019男人天堂| 欧美巨大xxxx| 欧美成人r级一区二区三区| 69久久久久久| 水蜜桃在线视频| 亚洲一卡二卡三卡四卡无卡久久| 宅男av一区二区三区| 免费在线黄色影片| av在线播放一区二区三区| 97碰碰视频| 99久久久无码国产精品免费| 久久精品久久99精品久久| 国产精品91免费在线| 亚洲久久在线观看| 亚洲另类视频| 性色av一区二区三区免费| 福利所第一导航| 欧美~级网站不卡| 欧美www在线| 国产人妻精品一区二区三区不卡| 自拍偷拍激情视频| 久久天天躁日日躁| 亚洲综合自拍一区| av网站在线不卡| 免费成人av电影| a级高清视频欧美日韩| 亚洲自拍小视频| 国产麻豆免费观看| 国内不卡的二区三区中文字幕| 国产精品久久久久久av下载红粉 | 尤物在线精品| 97在线免费观看视频| 国产精选第一页| 亚洲韩日在线| 欧美亚洲成人xxx| 欧美精品一二三四区| 欧美丰满熟妇xxxxx| 日韩成人av在线播放| av综合在线播放| 成人日日夜夜| 国产亚洲自拍av| 成人小视频在线观看免费| 久久久免费在线观看| 亚洲第一激情av| 久精品国产欧美| 日本人妖在线| 久久久久久久久蜜桃| 日本10禁啪啪无遮挡免费一区二区| 九一国产在线| 中文字幕欧美日韩一区| 中文字幕一区二区三区最新| 羞羞的网站在线观看| 婷婷综合在线观看| 国产精品乱码久久久久| 久久久久毛片| 欧美大片在线观看一区| 亚洲欧美色图视频| 精品国产一区探花在线观看| 久久久精品久久久| 日本一区二区三区免费视频| 日韩专区欧美专区| 51蜜桃传媒精品一区二区| 视频污在线观看| 国产清纯白嫩初高生在线观看91| 色中文字幕在线观看| 女同一区二区免费aⅴ| 欧美性xxxxx极品娇小| 黄色片视频在线| xxxx日韩| 中文字幕亚洲字幕| 久久久99精品| 男人操女人的视频在线观看欧美 | 五月婷婷激情五月| 国产一区二区三区美女| 蜜桃视频在线观看91| 激情影院在线观看| 福利微拍一区二区| 色偷偷中文字幕| 欧美猛男做受videos| 欧美人在线观看| 最近中文字幕在线免费观看| 波多野结衣精品在线| 99久久久无码国产精品性色戒| 在线观看涩涩| 欧美mv日韩mv国产网站| 三级黄色片在线观看| 99热这里只有成人精品国产| 成人久久一区二区| 国产一区二区三区不卡在线| 午夜一区二区三区在线观看| 色偷偷中文字幕| 色爱综合网欧美| 日韩av免费看| 婷婷色在线视频| 亚洲精品老司机| 亚洲精品久久久中文字幕| 全球av集中精品导航福利| 欧美成人午夜激情在线| 亚洲天堂自拍偷拍| 久久美女艺术照精彩视频福利播放| 777久久精品一区二区三区无码| 91成人在线| 亚洲人精品午夜在线观看| 自拍偷拍欧美亚洲| 粉嫩欧美一区二区三区高清影视| 性欧美18一19内谢| 国产精品久久久久久久久久齐齐| 亚洲男人第一av网站| 日韩 欧美 综合| 成人少妇影院yyyy| 日韩a级黄色片| 欧美电影院免费观看| 日韩一区二区三区在线播放| 中文字幕av在线免费观看| 久久久噜噜噜久久人人看| 97国产精东麻豆人妻电影| 大奶在线精品| 97视频在线观看成人| 亚洲男女视频在线观看| 一卡二卡三卡日韩欧美| 久久久久久久穴| 欧美日韩三级电影在线| 成人综合av网| 国产色播av在线| 日韩国产欧美区| 草久视频在线观看| 久久中文娱乐网| 亚洲人成色77777| 国产一区二区精品福利地址| 国产精品久久久久久久9999| 97电影在线看视频| 欧美日韩国产美| 91 在线视频| 丰满白嫩尤物一区二区| 国产精品又粗又长| 丝袜连裤袜欧美激情日韩| 欧美专区中文字幕| 国产特黄在线| 7777精品伊人久久久大香线蕉经典版下载 | 一区二区三区四区国产精品| 久久久久亚洲AV成人网人人小说| 亚洲高清激情| 日本一区视频在线播放| 色综合一区二区日本韩国亚洲| 久久国产天堂福利天堂| 欧美一级免费片| 色一情一伦一子一伦一区| 大吊一区二区三区| 国产91精品露脸国语对白| 免费无码不卡视频在线观看| 激情五月综合网| 91精品在线播放| 看黄在线观看| 中文字幕日韩有码| 亚洲精品国产一区二| 欧美日韩亚洲精品内裤| 制服丨自拍丨欧美丨动漫丨| 成人小视频免费在线观看| 99爱视频在线| 天天做天天爱天天综合网2021| 国产美女精品在线观看| 成人做爰视频www| 欧美人在线视频| 国产黄色免费在线观看| 日韩欧美在线网站| 潘金莲一级淫片aaaaaa播放| 亚洲天天做日日做天天谢日日欢| fc2成人免费视频| 青青草成人在线观看| 97干在线视频| 国产精品99在线观看| 久久香蕉综合色| 久久天堂久久| 国产成人精品国内自产拍免费看| 91精品国产91久久久久久青草| 日韩电影网在线| 亚洲第一成年人网站| 欧美性猛交xxxx黑人交 | 色播五月激情综合网| 精品欧美一区二区久久久久| 国产婷婷一区二区| 亚洲香蕉中文网| 国模少妇一区二区三区| 日韩手机在线观看视频| 亚洲欧洲一级| 99re6这里有精品热视频| 精品国产一区二区三区av片| 精品国产一区二区三区麻豆小说| av国产精品| 国产成人精品av在线| 久草在线资源福利站| 欧美精品制服第一页| 在线观看精品一区二区三区| 亚洲美女性视频| 丰满肉肉bbwwbbww| 91精品国产综合久久久久久久久久| 无码人妻丰满熟妇精品区| 欧美日韩精品在线播放| 欧美日韩一级在线观看| |精品福利一区二区三区| 国产一级淫片久久久片a级| 久久久美女毛片 | 中文字幕人成不卡一区| 韩国三级hd中文字幕| www精品美女久久久tv| 少妇精品无码一区二区三区| 成人永久看片免费视频天堂| 91免费视频污| 国产一区二区精品在线观看| 九一精品久久久| 久久精品二区亚洲w码| 国产三级国产精品国产专区50| 日本中文字幕一区二区视频| 国产日韩成人内射视频| 免费永久网站黄欧美| 黄色免费观看视频网站| 亚洲欧美日本视频在线观看| 国产精品又粗又长| 日韩亚洲国产精品| 91九色在线观看视频| 国产免费成人| 国产精品亚洲a| 久久中文在线| 日本免费观看网站| 美国欧美日韩国产在线播放| 亚洲欧美日本一区二区三区| 国产呦萝稀缺另类资源| 久久精品无码一区二区三区毛片| 国产激情视频一区二区三区欧美 | 成人午夜网址| 国产在线观看一区| 欧美三级午夜理伦三级在线观看 | 免费在线看黄网站| 精品国产一区二区三区久久久 | 国内精品国产三级国产aⅴ久| 国产一区二区免费视频| 精品人妻人人做人人爽夜夜爽| 国产99久久精品| 丝袜美腿中文字幕| 日本一区二区三级电影在线观看 | 精品999成人| 欧美日韩第二页| 美女脱光内衣内裤视频久久网站 | 2022亚洲天堂| 日韩制服丝袜先锋影音| 午夜视频在线网站| 国产91精品一区二区| 成年人网站免费看| 国产精品视频麻豆| 麻豆亚洲av熟女国产一区二| 福利二区91精品bt7086| 在线观看国产精品视频| 日韩女优av电影| 麻豆app在线观看| 久久久久999| 狠狠操一区二区三区| 国产精品露脸自拍| 中文在线免费一区三区| 热re99久久精品国产99热| 欧美3p在线观看| 99久久国产综合精品五月天喷水| 日韩精品三区四区| 国产伦理在线观看| 国产日韩欧美亚洲| 黄色一级片在线免费观看| 精品免费在线视频| 一本色道久久综合亚洲| 亚洲电影成人av99爱色| aaa日本高清在线播放免费观看| 色综合久综合久久综合久鬼88| 桃色一区二区| 国产免费一区二区三区| 999久久久亚洲| 亚洲欧洲日产国码无码久久99| 国产一区二区调教| 欧美狂猛xxxxx乱大交3| 一区二区欧美在线观看| 中文字幕精品一区二| 日韩电影在线观看永久视频免费网站| 蜜桃av在线免费观看| 青草青草久热精品视频在线观看| 久久免费精品| 伊人天天久久大香线蕉av色| 久久九九精品| v天堂中文在线| 亚洲另类在线视频| 中文字幕一区二区在线视频 | 中文字幕日韩三级片| 亚洲欧美日韩国产一区二区三区| 69视频免费看| 亚洲精品久久久久中文字幕欢迎你 | 国产成人精品亚洲精品色欲| 这里只有精品久久| 欧美特黄aaaaaaaa大片| 精品国产乱码久久久久久丨区2区 精品国产乱码久久久久久蜜柚 | 亚洲欧美综合图区| 岛国毛片av在线| 91在线播放视频| 亚洲二区三区不卡| 天天色综合社区| 国产三级欧美三级| 日本中文字幕久久| 亚洲女成人图区| 中文字幕在线视频久| 黄色小网站91| 亚洲另类视频| 久久久久成人精品无码中文字幕| 亚洲欧美日韩电影| 国产免费一区二区三区最新不卡| 色婷婷综合久久久久| 国产亚洲欧美日韩精品一区二区三区| 欧美精品v日韩精品v国产精品| 香蕉久久国产| 醉酒壮男gay强迫野外xx| 欧美日韩免费看| 青青草超碰在线| 国产不卡av在线| sdde在线播放一区二区| 看欧美ab黄色大片视频免费 | 视频在线日韩| 无遮挡亚洲一区| 美女性感视频久久| 欧洲美女女同性互添| 日韩欧美中文一区| 国产高清在线a视频大全| 国产麻豆日韩| 亚洲欧美成人| 久久视频一区二区三区| 欧美人xxxx| 羞羞视频在线观看不卡| 国产麻豆乱码精品一区二区三区| 一本色道久久| 亚洲精品91在线| 91麻豆精品91久久久久久清纯 | 精品999视频| 国产精品欧美激情在线播放| 香蕉国产精品| 久久久久亚洲AV成人网人人小说| 精品成人乱色一区二区| 免费在线超碰| 成人午夜激情网| 亚洲茄子视频| 偷拍夫妻性生活| 在线电影院国产精品| av影视在线看| 日韩av电影免费在线| 精品一区二区成人精品| 国产精品111| 国产亚洲欧美aaaa| 精品国产欧美| 国产l精品国产亚洲区久久| 欧美激情一区二区三区蜜桃视频 | 日韩乱码在线视频| 成人1区2区| 国产欧美日韩小视频| 国产视频一区二区在线| 国产老妇伦国产熟女老妇视频| 久久久久久亚洲精品中文字幕| 精品在线观看入口| 欧美日韩久久婷婷| 精品久久久久久中文字幕一区奶水| yjizz视频网站在线播放| 91亚色免费| 免费美女久久99| 日韩欧美视频在线免费观看| 中文字幕在线国产精品| 国产精品白丝一区二区三区| 成年网站在线播放| 亚洲va欧美va人人爽| 91在线看片| 欧美1o一11sex性hdhd|