1. 程式人生 > 程式設計 >golang中context的作用詳解

golang中context的作用詳解

當一個goroutine可以啟動其他goroutine,而這些goroutine可以啟動其他goroutine,依此類推,則第一個goroutine應該能夠向所有其它goroutine傳送取消訊號。

上下文包的唯一目的是在goroutine之間執行取消訊號,而不管它們如何生成。上下文的介面定義為:

type Context interface {
 Deadline() (deadline time.Time,ok bool)
 Done() <- chan struct{}
 Err() error
 Value(key interface{}) interface{}
}
  • Deadline:第一個值是截止日期,此時上下文將自動觸發“取消”操作。第二個值是布林值,true表示設定了截止日期,false表示未設定截止時間。如果沒有設定截止日期,則必須手動呼叫cancel函式來取消上下文。
  • Done:返回一個只讀通道(僅在取消後),鍵入struct {},當該通道可讀時,表示父上下文已經發起了取消請求,根據此訊號,開發人員可以執行一些清除操作,退出goroutine
  • Err:返回取消上下文的原因
  • Value:返回繫結到上下文的值,它是一個鍵值對,因此您需要傳遞一個Key來獲取相應的值,此值是執行緒安全的

要建立上下文,必須指定父上下文。兩個內建上下文(背景和待辦事項)用作頂級父上下文:

var (
 background = new(emptyCtx)
 todo = new(emptyCtx)
)
func Background() Context {
 return background
}
func TODO() Context {
 return todo
}

背景,主要ü在主函式,初始化和測試程式碼的sed,是樹結構中,根上下文,這是不能被取消的頂層語境。TODO,當您不知道要使用什麼上下文時,可以使用它。它們本質上都是emptyCtx型別,都是不可取消的,沒有固定的期限,也沒有為Context賦任何值:鍵入emptyCtx int

type emptyCtx int
func (_ *emptyCtx) Deadline() (deadline time.Time,ok bool) {
 return
}
func (_ *emptyCtx) Done() <- chan struct{} {
 return nil
}
func (_ *emptyCtx) Err() error {
 return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
 return nil
}

上下文包還具有幾個常用功能:func WithCancel(父上下文)(ctx上下文,取消CancelFunc)func WithDeadline(父上下文,截止時間.Time)(上下文,CancelFunc)func WithTimeout(父上下文,超時時間。持續時間)(上下文,CancelFunc)func WithValue(父上下文,鍵,val介面{})上下文

請注意,這些方法意味著可以一次繼承上下文以實現其他功能,例如,使用WithCancel函式傳入根上下文,它會建立一個子上下文,該子上下文具有取消上下文的附加功能,然後使用此方法將context(context01)作為父上下文,並將其作為第一個引數傳遞給WithDeadline函式,與子context(context01)相比,獲得子context(context02),它具有一個附加功能,可在之後自動取消上下文最後期限。

WithCancel

對於通道,儘管通道也可以通知許多巢狀的goroutine退出,但通道不是執行緒安全的,而上下文是執行緒安全的。

例如:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ch chan bool,index int) {
 for {
  select {
  case v := <- ch:
   fmt.Printf("monitor2: %v,the received channel value is: %v,ending\n",index,v)
   return
  default:
   fmt.Printf("monitor2: %v in progress...\n",index)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ch chan bool,index int) {
 for {
  go monitor2(ch,index)
  select {
  case v := <- ch:
   // this branch is only reached when the ch channel is closed,or when data is sent(either true or false)
   fmt.Printf("monitor1: %v,v)
   return
  default:
   fmt.Printf("monitor1: %v in progress...\n",index)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var stopSingal chan bool = make(chan bool,0)
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(stopSingal,i)
 }
 time.Sleep(1 * time.Second)
 // close all gourtines
 cancel()
 // waiting 10 seconds,if the screen does not display <monitorX: xxxx in progress...>,all goroutines have been shut down
 time.Sleep(10 * time.Second)
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

執行的結果是:

monitor1: 5 in progress...
monitor2: 5 in progress...
monitor1: 2 in progress...
monitor2: 2 in progress...
monitor2: 1 in progress...
monitor1: 1 in progress...
monitor1: 4 in progress...
monitor1: 3 in progress...
monitor2: 4 in progress...
monitor2: 3 in progress...
monitor1: 4,the received channel value is: false,ending
monitor1: 3,ending
monitor2: 2,ending
monitor2: 1,ending
monitor1: 1,ending
monitor2: 5,ending
monitor2: 3,ending
monitor2: 4,ending
monitor1: 5,ending
monitor1: 2,ending
1
main program exit!!!!

這裡使用一個通道向所有goroutine傳送結束通知,但是這裡的情況相對簡單,如果在一個複雜的專案中,假設多個goroutine有某種錯誤並重復執行,則可以重複關閉或關閉該通道通道,然後向其寫入值,從而觸發執行時恐慌。這就是為什麼我們使用上下文來避免這些問題的原因,以WithCancel為例:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ctx context.Context,number int) {
 for {
  select {
  case v := <- ctx.Done():
   fmt.Printf("monitor: %v,number,v)
   return
  default:
   fmt.Printf("monitor: %v in progress...\n",number)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ctx context.Context,number int) {
 for {
  go monitor2(ctx,number)
  select {
  case v := <- ctx.Done():
   // this branch is only reached when the ch channel is closed,or when data is sent(either true or false)
   fmt.Printf("monitor: %v,number)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx context.Context = nil
 var cancel context.CancelFunc = nil
 ctx,cancel = context.WithCancel(context.Background())
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(ctx,if the screen does not display <monitor: xxxx in progress>,all goroutines have been shut down
 time.Sleep(10 * time.Second)
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

WithTimeout和WithDeadline

WithTimeout和WithDeadline在用法和功能上基本相同,它們都表示上下文將在一定時間後自動取消,唯一的區別可以從函式的定義中看出,傳遞給WithDeadline的第二個引數是型別time.Duration型別,它是一個相對時間,表示取消超時後的時間。例:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ctx context.Context,index int) {
 for {
  select {
  case v := <- ctx.Done():
   fmt.Printf("monitor2: %v,index)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ctx context.Context,index int) {
 for {
  go monitor2(ctx,index)
  select {
  case v := <- ctx.Done():
   // this branch is only reached when the ch channel is closed,index)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx01 context.Context = nil
 var ctx02 context.Context = nil
 var cancel context.CancelFunc = nil
 ctx01,cancel = context.WithCancel(context.Background())
 ctx02,cancel = context.WithDeadline(ctx01,time.Now().Add(1 * time.Second)) // If it's WithTimeout,just change this line to "ctx02,cancel = context.WithTimeout(ctx01,1 * time.Second)"
 defer cancel()
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(ctx02,i)
 }
 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
  fmt.Println("the cause of cancel is: ",ctx02.Err())
 }
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

WithValue

一些必需的元資料也可以通過上下文傳遞,該上下文將附加到上下文中以供使用。元資料作為鍵值傳遞,但請注意,鍵必須具有可比性,並且值必須是執行緒安全的。

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor(ctx context.Context,index int) {
 for {
  select {
  case <- ctx.Done():
   // this branch is only reached when the ch channel is closed,or when data is sent(either true or false)
   fmt.Printf("monitor %v,end of monitoring. \n",index)
   return
  default:
   var value interface{} = ctx.Value("Nets")
   fmt.Printf("monitor %v,is monitoring %v\n",value)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx01 context.Context = nil
 var ctx02 context.Context = nil
 var cancel context.CancelFunc = nil
 ctx01,1 * time.Second)
 var ctx03 context.Context = context.WithValue(ctx02,"Nets","Champion") // key: "Nets",value: "Champion"

 defer cancel()
 for i := 1; i <= 5; i = i + 1 {
  go monitor(ctx03,ctx02.Err())
 }
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

關於上下文,還有一些注意事項:不要將Context儲存在結構型別中,而是將Context明確傳遞給需要它的每個函式,並且Context應該是第一個引數。

即使函式允許,也不要傳遞nil Context,或者如果您不確定要使用哪個Context,請傳遞context。不要將可能作為函式引數傳遞給上下文值的變數傳遞。

到此這篇關於golang中context的作用的文章就介紹到這了,更多相關golang中context的作用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!