欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

Go語言中的Context的詳細(xì)解析

這篇文章主要講解了 Go 語言中的Context的詳細(xì)解析,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。

成都創(chuàng)新互聯(lián)是一家專業(yè)提供南澗企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計、成都網(wǎng)站制作、H5場景定制、小程序制作等業(yè)務(wù)。10年已為南澗眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。

1. 什么是 Context?

在 Go 1.7 版本之前,context 還是非編制的,它存在于 golang.org/x/net/context 包中。

后來,Golang 團(tuán)隊發(fā)現(xiàn) context 還挺好用的,就把 context 收編了,在 Go 1.7 版本正式納入了標(biāo)準(zhǔn)庫。

Context,也叫上下文,它的接口定義如下

type Context interface {
 Deadline() (deadline time.Time, ok bool)
 Done() <-chan struct{}
 Err() error
 Value(key interface{}) interface{}
}

可以看到 Context 接口共有 4 個方法

  • Deadline:返回的第一個值是 截止時間,到了這個時間點,Context 會自動觸發(fā) Cancel 動作。返回的第二個值是 一個布爾值,true 表示設(shè)置了截止時間,false 表示沒有設(shè)置截止時間,如果沒有設(shè)置截止時間,就要手動調(diào)用 cancel 函數(shù)取消 Context。
  • Done:返回一個只讀的通道(只有在被cancel后才會返回),類型為 struct{}。當(dāng)這個通道可讀時,意味著parent context已經(jīng)發(fā)起了取消請求,根據(jù)這個信號,開發(fā)者就可以做一些清理動作,退出goroutine。
  • Err:返回 context 被 cancel 的原因。
  • Value:返回被綁定到 Context 的值,是一個鍵值對,所以要通過一個Key才可以獲取對應(yīng)的值,這個值一般是線程安全的。

2. 為何需要 Context?

當(dāng)一個協(xié)程(goroutine)開啟后,我們是無法強制關(guān)閉它的。

常見的關(guān)閉協(xié)程的原因有如下幾種:

  • goroutine 自己跑完結(jié)束退出
  • 主進(jìn)程crash退出,goroutine 被迫退出
  • 通過通道發(fā)送信號,引導(dǎo)協(xié)程的關(guān)閉。

第一種,屬于正常關(guān)閉,不在今天討論范圍之內(nèi)。

第二種,屬于異常關(guān)閉,應(yīng)當(dāng)優(yōu)化代碼。

第三種,才是開發(fā)者可以手動控制協(xié)程的方法,代碼示例如下:

func main() {
 stop := make(chan bool)

 go func() {
 for {
 select {
 case <-stop:
 fmt.Println("監(jiān)控退出,停止了...")
 return
 default:
 fmt.Println("goroutine監(jiān)控中...")
 time.Sleep(2 * time.Second)
 }
 }
 }()

 time.Sleep(10 * time.Second)
 fmt.Println("可以了,通知監(jiān)控停止")
 stop<- true
 //為了檢測監(jiān)控過是否停止,如果沒有監(jiān)控輸出,就表示停止了
 time.Sleep(5 * time.Second)

}

例子中我們定義一個stop的chan,通知他結(jié)束后臺goroutine。實現(xiàn)也非常簡單,在后臺goroutine中,使用select判斷stop是否可以接收到值,如果可以接收到,就表示可以退出停止了;如果沒有接收到,就會執(zhí)行default里的監(jiān)控邏輯,繼續(xù)監(jiān)控,只到收到stop的通知。

以上是一個 goroutine 的場景,如果是多個 goroutine ,每個goroutine 底下又開啟了多個 goroutine 的場景呢?在 飛雪無情的博客 里關(guān)于為何要使用 Context,他是這么說的

chan+select的方式,是比較優(yōu)雅的結(jié)束一個goroutine的方式,不過這種方式也有局限性,如果有很多goroutine都需要控制結(jié)束怎么辦呢?如果這些goroutine又衍生了其他更多的goroutine怎么辦呢?如果一層層的無窮盡的goroutine呢?這就非常復(fù)雜了,即使我們定義很多chan也很難解決這個問題,因為goroutine的關(guān)系鏈就導(dǎo)致了這種場景非常復(fù)雜。

在這里我不是很贊同他說的話,因為我覺得就算只使用一個通道也能達(dá)到控制(取消)多個 goroutine 的目的。下面就用例子來驗證一下。

該例子的原理是:使用 close 關(guān)閉通道后,如果該通道是無緩沖的,則它會從原來的阻塞變成非阻塞,也就是可讀的,只不過讀到的會一直是零值,因此根據(jù)這個特性就可以判斷 擁有該通道的 goroutine 是否要關(guān)閉。

package main

import (
 "fmt"
 "time"
)

func monitor(ch chan bool, number int) {
 for {
 select {
 case v := <-ch:
 // 僅當(dāng) ch 通道被 close,或者有數(shù)據(jù)發(fā)過來(無論是true還是false)才會走到這個分支
 fmt.Printf("監(jiān)控器%v,接收到通道值為:%v,監(jiān)控結(jié)束。\n", number,v)
 return
 default:
 fmt.Printf("監(jiān)控器%v,正在監(jiān)控中...\n", number)
 time.Sleep(2 * time.Second)
 }
 }
}

func main() {
 stopSingal := make(chan bool)

 for i :=1 ; i <= 5; i++ {
 go monitor(stopSingal, i)
 }

 time.Sleep( 1 * time.Second)
 // 關(guān)閉所有 goroutine
 close(stopSingal)

 // 等待5s,若此時屏幕沒有輸出 <正在監(jiān)控中> 就說明所有的goroutine都已經(jīng)關(guān)閉
 time.Sleep( 5 * time.Second)

 fmt.Println("主程序退出?。?quot;)

}

輸出如下

監(jiān)控器4,正在監(jiān)控中...
監(jiān)控器1,正在監(jiān)控中...
監(jiān)控器2,正在監(jiān)控中...
監(jiān)控器3,正在監(jiān)控中...
監(jiān)控器5,正在監(jiān)控中...
監(jiān)控器2,接收到通道值為:false,監(jiān)控結(jié)束。
監(jiān)控器3,接收到通道值為:false,監(jiān)控結(jié)束。
監(jiān)控器5,接收到通道值為:false,監(jiān)控結(jié)束。
監(jiān)控器1,接收到通道值為:false,監(jiān)控結(jié)束。
監(jiān)控器4,接收到通道值為:false,監(jiān)控結(jié)束。
主程序退出??!

上面的例子,說明當(dāng)我們定義一個無緩沖通道時,如果要對所有的 goroutine 進(jìn)行關(guān)閉,可以使用 close 關(guān)閉通道,然后在所有的 goroutine 里不斷檢查通道是否關(guān)閉(前提你得約定好,該通道你只會進(jìn)行 close 而不會發(fā)送其他數(shù)據(jù),否則發(fā)送一次數(shù)據(jù)就會關(guān)閉一個goroutine,這樣會不符合咱們的預(yù)期,所以最好你對這個通道再做一層封裝做個限制)來決定是否結(jié)束 goroutine。

所以你看到這里,我做為初學(xué)者還是沒有找到使用 Context 的必然理由,我只能說 Context 是個很好用的東西,使用它方便了我們在處理并發(fā)時候的一些問題,但是它并不是不可或缺的。

換句話說,它解決的并不是 能不能 的問題,而是解決 更好用 的問題。

3. 簡單使用 Context

如果不使用上面 close 通道的方式,還有沒有其他更優(yōu)雅的方法來實現(xiàn)呢?

有,那就是本文要講的 Context

我使用 Context 對上面的例子進(jìn)行了一番改造。

package main

import (
 "context"
 "fmt"
 "time"
)

func monitor(ctx context.Context, number int) {
 for {
 select {
 // 其實可以寫成 case <- ctx.Done()
 // 這里僅是為了讓你看到 Done 返回的內(nèi)容
 case v :=<- ctx.Done():
 fmt.Printf("監(jiān)控器%v,接收到通道值為:%v,監(jiān)控結(jié)束。\n", number,v)
 return
 default:
 fmt.Printf("監(jiān)控器%v,正在監(jiān)控中...\n", number)
 time.Sleep(2 * time.Second)
 }
 }
}

func main() {
 ctx, cancel := context.WithCancel(context.Background())

 for i :=1 ; i <= 5; i++ {
 go monitor(ctx, i)
 }

 time.Sleep( 1 * time.Second)
 // 關(guān)閉所有 goroutine
 cancel()

 // 等待5s,若此時屏幕沒有輸出 <正在監(jiān)控中> 就說明所有的goroutine都已經(jīng)關(guān)閉
 time.Sleep( 5 * time.Second)

 fmt.Println("主程序退出!!")

}

這里面的關(guān)鍵代碼,也就三行

第一行:以 context.Background() 為 parent context 定義一個可取消的 context

ctx, cancel := context.WithCancel(context.Background())

第二行:然后你可以在所有的goroutine 里利用 for + select 搭配來不斷檢查 ctx.Done() 是否可讀,可讀就說明該 context 已經(jīng)取消,你可以清理 goroutine 并退出了。

case <- ctx.Done():

第三行:當(dāng)你想到取消 context 的時候,只要調(diào)用一下 cancel 方法即可。這個 cancel 就是我們在創(chuàng)建 ctx 的時候返回的第二個值。

cancel()

運行結(jié)果輸出如下??梢园l(fā)現(xiàn)我們實現(xiàn)了和 close 通道一樣的效果。

監(jiān)控器3,正在監(jiān)控中...
監(jiān)控器4,正在監(jiān)控中...
監(jiān)控器1,正在監(jiān)控中...
監(jiān)控器2,正在監(jiān)控中...
監(jiān)控器2,接收到通道值為:{},監(jiān)控結(jié)束。
監(jiān)控器5,接收到通道值為:{},監(jiān)控結(jié)束。
監(jiān)控器4,接收到通道值為:{},監(jiān)控結(jié)束。
監(jiān)控器1,接收到通道值為:{},監(jiān)控結(jié)束。
監(jiān)控器3,接收到通道值為:{},監(jiān)控結(jié)束。
主程序退出!!

4. 根Context 是什么?

創(chuàng)建 Context 必須要指定一個 父 Context,當(dāng)我們要創(chuàng)建第一個Context時該怎么辦呢?

不用擔(dān)心,Go 已經(jīng)幫我們實現(xiàn)了2個,我們代碼中最開始都是以這兩個內(nèi)置的context作為最頂層的parent context,衍生出更多的子Context。

var (
 background = new(emptyCtx)
 todo = new(emptyCtx)
)

func Background() Context {
 return background
}

func TODO() Context {
 return todo
}

一個是Background,主要用于main函數(shù)、初始化以及測試代碼中,作為Context這個樹結(jié)構(gòu)的最頂層的Context,也就是根Context,它不能被取消。

一個是TODO,如果我們不知道該使用什么Context的時候,可以使用這個,但是實際應(yīng)用中,暫時還沒有使用過這個TODO。

他們兩個本質(zhì)上都是emptyCtx結(jié)構(gòu)體類型,是一個不可取消,沒有設(shè)置截止時間,沒有攜帶任何值的Context。

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
}

5. Context 的繼承衍生

上面在定義我們自己的 Context 時,我們使用的是 WithCancel 這個方法。

除它之外,context 包還有其他幾個 With 系列的函數(shù)

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

這四個函數(shù)有一個共同的特點,就是第一個參數(shù),都是接收一個 父context。

通過一次繼承,就多實現(xiàn)了一個功能,比如使用 WithCancel 函數(shù)傳入 根context ,就創(chuàng)建出了一個子 context,該子context 相比 父context,就多了一個 cancel context 的功能。

如果此時,我們再以上面的子context(context01)做為父context,并將它做為第一個參數(shù)傳入WithDeadline函數(shù),獲得的子子context(context02),相比子context(context01)而言,又多出了一個超過 deadline 時間后,自動 cancel context 的功能。

接下來我會舉例介紹一下這幾種 context,其中 WithCancel 在上面已經(jīng)講過了,下面就不再舉例了

例子 1:WithDeadline

package main

import (
 "context"
 "fmt"
 "time"
)

func monitor(ctx context.Context, number int) {
 for {
 select {
 case <- ctx.Done():
 fmt.Printf("監(jiān)控器%v,監(jiān)控結(jié)束。\n", number)
 return
 default:
 fmt.Printf("監(jiān)控器%v,正在監(jiān)控中...\n", number)
 time.Sleep(2 * time.Second)
 }
 }
}

func main() {
 ctx01, cancel := context.WithCancel(context.Background())
 ctx02, cancel := context.WithDeadline(ctx01, time.Now().Add(1 * time.Second))

 defer cancel()

 for i :=1 ; i <= 5; i++ {
 go monitor(ctx02, i)
 }

 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
 fmt.Println("監(jiān)控器取消的原因: ", ctx02.Err())
 }

 fmt.Println("主程序退出??!")
}

輸出如下

監(jiān)控器5,正在監(jiān)控中...
監(jiān)控器1,正在監(jiān)控中...
監(jiān)控器2,正在監(jiān)控中...
監(jiān)控器3,正在監(jiān)控中...
監(jiān)控器4,正在監(jiān)控中...
監(jiān)控器3,監(jiān)控結(jié)束。
監(jiān)控器4,監(jiān)控結(jié)束。
監(jiān)控器2,監(jiān)控結(jié)束。
監(jiān)控器1,監(jiān)控結(jié)束。
監(jiān)控器5,監(jiān)控結(jié)束。
監(jiān)控器取消的原因:  context deadline exceeded
主程序退出?。?/p>

例子 2:WithTimeout

WithTimeout 和 WithDeadline 使用方法及功能基本一致,都是表示超過一定的時間會自動 cancel context。

唯一不同的地方,我們可以從函數(shù)的定義看出

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithDeadline 傳入的第二個參數(shù)是 time.Time 類型,它是一個絕對的時間,意思是在什么時間點超時取消。

而 WithTimeout 傳入的第二個參數(shù)是 time.Duration 類型,它是一個相對的時間,意思是多長時間后超時取消。

package main

import (
 "context"
 "fmt"
 "time"
)

func monitor(ctx context.Context, number int) {
 for {
 select {
 case <- ctx.Done():
 fmt.Printf("監(jiān)控器%v,監(jiān)控結(jié)束。\n", number)
 return
 default:
 fmt.Printf("監(jiān)控器%v,正在監(jiān)控中...\n", number)
 time.Sleep(2 * time.Second)
 }
 }
}

func main() {
 ctx01, cancel := context.WithCancel(context.Background())
 
 // 相比例子1,僅有這一行改動
 ctx02, cancel := context.WithTimeout(ctx01, 1* time.Second)

 defer cancel()

 for i :=1 ; i <= 5; i++ {
 go monitor(ctx02, i)
 }

 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
 fmt.Println("監(jiān)控器取消的原因: ", ctx02.Err())
 }

 fmt.Println("主程序退出!!")
}

輸出的結(jié)果和上面一樣

 監(jiān)控器1,正在監(jiān)控中...
監(jiān)控器5,正在監(jiān)控中...
監(jiān)控器3,正在監(jiān)控中...
監(jiān)控器2,正在監(jiān)控中...
監(jiān)控器4,正在監(jiān)控中...
監(jiān)控器4,監(jiān)控結(jié)束。
監(jiān)控器2,監(jiān)控結(jié)束。
監(jiān)控器5,監(jiān)控結(jié)束。
監(jiān)控器1,監(jiān)控結(jié)束。
監(jiān)控器3,監(jiān)控結(jié)束。
監(jiān)控器取消的原因:  context deadline exceeded
主程序退出??!

例子 3:WithValue

通過Context我們也可以傳遞一些必須的元數(shù)據(jù),這些數(shù)據(jù)會附加在Context上以供使用。

元數(shù)據(jù)以 Key-Value 的方式傳入,Key 必須有可比性,Value 必須是線程安全的。

還是用上面的例子,以 ctx02 為父 context,再創(chuàng)建一個能攜帶 value 的ctx03,由于他的父context 是 ctx02,所以 ctx03 也具備超時自動取消的功能。

package main

import (
 "context"
 "fmt"
 "time"
)

func monitor(ctx context.Context, number int) {
 for {
 select {
 case <- ctx.Done():
 fmt.Printf("監(jiān)控器%v,監(jiān)控結(jié)束。\n", number)
 return
 default:
 // 獲取 item 的值
 value := ctx.Value("item")
 fmt.Printf("監(jiān)控器%v,正在監(jiān)控 %v \n", number, value)
 time.Sleep(2 * time.Second)
 }
 }
}

func main() {
 ctx01, cancel := context.WithCancel(context.Background())
 ctx02, cancel := context.WithTimeout(ctx01, 1* time.Second)
 ctx03 := context.WithValue(ctx02, "item", "CPU")

 defer cancel()

 for i :=1 ; i <= 5; i++ {
 go monitor(ctx03, i)
 }

 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
 fmt.Println("監(jiān)控器取消的原因: ", ctx02.Err())
 }

 fmt.Println("主程序退出??!")
}

輸出如下

監(jiān)控器4,正在監(jiān)控 CPU
監(jiān)控器5,正在監(jiān)控 CPU
監(jiān)控器1,正在監(jiān)控 CPU
監(jiān)控器3,正在監(jiān)控 CPU
監(jiān)控器2,正在監(jiān)控 CPU
監(jiān)控器2,監(jiān)控結(jié)束。
監(jiān)控器5,監(jiān)控結(jié)束。
監(jiān)控器3,監(jiān)控結(jié)束。
監(jiān)控器1,監(jiān)控結(jié)束。
監(jiān)控器4,監(jiān)控結(jié)束。
監(jiān)控器取消的原因:  context deadline exceeded
主程序退出??!

6. Context 使用注意事項

  • 通常 Context 都是做為函數(shù)的第一個參數(shù)進(jìn)行傳遞(規(guī)范性做法),并且變量名建議統(tǒng)一叫 ctx
  • Context 是線程安全的,可以放心地在多個 goroutine 中使用。
  • 當(dāng)你把 Context 傳遞給多個 goroutine 使用時,只要執(zhí)行一次 cancel 操作,所有的 goroutine 就可以收到 取消的信號
  • 不要把原本可以由函數(shù)參數(shù)來傳遞的變量,交給 Context 的 Value 來傳遞。
  • 當(dāng)一個函數(shù)需要接收一個 Context 時,但是此時你還不知道要傳遞什么 Context 時,可以先用 context.TODO 來代替,而不要選擇傳遞一個 nil。
  • 當(dāng)一個 Context 被 cancel 時,繼承自該 Context 的所有 子 Context 都會被 cancel。

看完上述內(nèi)容,是不是對 Go 語言中的Context的詳細(xì)解析有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)頁名稱:Go語言中的Context的詳細(xì)解析
網(wǎng)頁地址:http://aaarwkj.com/article18/pdeggp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、微信公眾號網(wǎng)站維護(hù)、網(wǎng)頁設(shè)計公司小程序開發(fā)、電子商務(wù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)
国产精品国产亚洲精品看不| 18禁在线免费观看网站| 国产精品蜜臀av在线一区| 亚洲精品一区二区三区高清| 91午夜福利国产在线观看| 年轻的少妇一区二区三区| 亚洲国产精品午夜福利在线播放 | 久久久久四虎国产精品| 国产精品一区二区剧情熟女| 国产三级网站在线观看| 人妻系列日本在线播放| 国产白丝精品爽爽久久| 日韩精品熟妻人女亚洲一区 | 五月激情开心久久婷婷| 麻豆视频传媒入口在线播放| 一区二区三区欧美影片| 精品毛片av一区二区三区| 一区二区三区日韩专区| 国产欧美日韩精品一区| 亚洲一区二区三区四区五区六| 99久久精品人妻一区| 久久精品亚洲熟女av蜜謦| 久国产精品韩国三级视频| 国产精品中文字幕第一页| 欧美一级特黄免费大片| 国产精品国产三级国av麻豆| 亚洲精品国产熟女久久| 激情网站免费在线观看| 亚洲欧美高清一区二区| 少妇精品久久久一区二区三区| 国产精品国产精品三级在线观看| 天天操夜夜操狠狠操91| 亚洲精品主播一区二区三区| 国产极品嫩模91精品| 天堂av免费资源在线观看| 国产精品白丝一区二区三区| 国产精品偷拍自拍视频| 日韩av网址在线免费观看| 国产成人短视频在线播放| 欧美日韩69av网| 国产一级r内射视频播放|