亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

Go 并發控制context實現原理剖析(小結)

2020-04-01 18:51:41
字體:
來源:轉載
供稿:網友

1. 前言

Golang context是Golang應用開發常用的并發控制技術,它與WaitGroup最大的不同點是context對于派生goroutine有更強的控制力,它可以控制多級的goroutine。

context翻譯成中文是"上下文",即它可以控制一組呈樹狀結構的goroutine,每個goroutine擁有相同的上下文。

典型的使用場景如下圖所示:

Go,并發控制,context

上圖中由于goroutine派生出子goroutine,而子goroutine又繼續派生新的goroutine,這種情況下使用WaitGroup就不太容易,因為子goroutine個數不容易確定。而使用context就可以很容易實現。

2. Context實現原理

context實際上只定義了接口,凡是實現該接口的類都可稱為是一種context,官方包中實現了幾個常用的context,分別可用于不同的場景。

2.1 接口定義

源碼包中src/context/context.go:Context 定義了該接口:

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

基礎的context接口只定義了4個方法,下面分別簡要說明一下:

2.1.1 Deadline()

該方法返回一個deadline和標識是否已設置deadline的bool值,如果沒有設置deadline,則ok == false,此時deadline為一個初始值的time.Time值

2.1.2 Done()

該方法返回一個channel,需要在select-case語句中使用,如"case <-context.Done():"。

當context關閉后,Done()返回一個被關閉的管道,關閉的管理仍然是可讀的,據此goroutine可以收到關閉請求;當context還未關閉時,Done()返回nil。

2.1.3 Err()

該方法描述context關閉的原因。關閉原因由context實現控制,不需要用戶設置。比如Deadline context,關閉原因可能是因為deadline,也可能提前被主動關閉,那么關閉原因就會不同:

  • 因deadline關閉:“context deadline exceeded”;
  • 因主動關閉: "context canceled"。

當context關閉后,Err()返回context的關閉原因;當context還未關閉時,Err()返回nil;

2.1.3 Value()

有一種context,它不是用于控制呈樹狀分布的goroutine,而是用于在樹狀分布的goroutine間傳遞信息。

Value()方法就是用于此種類型的context,該方法根據key值查詢map中的value。具體使用后面示例說明。

2.2 空context

context包中定義了一個空的context, 名為emptyCtx,用于context的根節點,空的context只是簡單的實現了Context,本身不包含任何值,僅用于其他context的父節點。

emptyCtx類型定義如下代碼所示:

type emptyCtx intfunc (*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}

context包中定義了一個公用的emptCtx全局變量,名為background,可以使用context.Background()獲取它,實現代碼如下所示:

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

context包提供了4個方法創建不同類型的context,使用這四個方法時如果沒有父context,都需要傳入backgroud,即backgroud作為其父節點:

  • WithCancel()
  • WithDeadline()
  • WithTimeout()
  • WithValue()

context包中實現Context接口的struct,除了emptyCtx外,還有cancelCtx、timerCtx和valueCtx三種,正是基于這三種context實例,實現了上述4種類型的context。

context包中各context類型之間的關系,如下圖所示:

Go,并發控制,context

struct cancelCtx、valueCtx、valueCtx都繼承于Context,下面分別介紹這三個struct。

2.3 cancelCtx

源碼包中src/context/context.go:cancelCtx 定義了該類型context:

type cancelCtx struct {	Context	mu    sync.Mutex      // protects following fields	done   chan struct{}     // created lazily, closed by first cancel call	children map[canceler]struct{} // set to nil by the first cancel call	err   error         // set to non-nil by the first cancel call}

children中記錄了由此context派生的所有child,此context被cancle時會把其中的所有child都cancle掉。

cancelCtx與deadline和value無關,所以只需要實現Done()和Err()接口外露接口即可。

2.3.1 Done()接口實現

按照Context定義,Done()接口只需要返回一個channel即可,對于cancelCtx來說只需要返回成員變量done即可。

這里直接看下源碼,非常簡單:

func (c *cancelCtx) Done() <-chan struct{} {	c.mu.Lock()	if c.done == nil {		c.done = make(chan struct{})	}	d := c.done	c.mu.Unlock()	return d}

由于cancelCtx沒有指定初始化函數,所以cancelCtx.done可能還未分配,所以需要考慮初始化。
cancelCtx.done會在context被cancel時關閉,所以cancelCtx.done的值一般經歷如三個階段:nil --> chan struct{} --> closed chan。

2.3.2 Err()接口實現

按照Context定義,Err()只需要返回一個error告知context被關閉的原因。對于cancelCtx來說只需要返回成員變量err即可。

還是直接看下源碼:

func (c *cancelCtx) Err() error {	c.mu.Lock()	err := c.err	c.mu.Unlock()	return err}

cancelCtx.err默認是nil,在context被cancel時指定一個error變量: var Canceled = errors.New("context canceled")。

2.3.3 cancel()接口實現

cancel()內部方法是理解cancelCtx的最關鍵的方法,其作用是關閉自己和其后代,其后代存儲在cancelCtx.children的map中,其中key值即后代對象,value值并沒有意義,這里使用map只是為了方便查詢而已。

cancel方法實現偽代碼如下所示:

func (c *cancelCtx) cancel(removeFromParent bool, err error) {  c.mu.Lock()	  c.err = err	           //設置一個error,說明關閉原因  close(c.done)           //將channel關閉,以此通知派生的context	  for child := range c.children {  //遍歷所有children,逐個調用cancel方法    child.cancel(false, err)  }  c.children = nil  c.mu.Unlock()  if removeFromParent {      //正常情況下,需要將自己從parent刪除    removeChild(c.Context, c)  }}

實際上,WithCancel()返回的第二個用于cancel context的方法正是此cancel()。

2.3.4 WithCancel()方法實現

WithCancel()方法作了三件事:

  • 初始化一個cancelCtx實例
  • 將cancelCtx實例添加到其父節點的children中(如果父節點也可以被cancel的話)
  • 返回cancelCtx實例和cancel()方法

其實現源碼如下所示:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {	c := newCancelCtx(parent)	propagateCancel(parent, &c)  //將自身添加到父節點	return &c, func() { c.cancel(true, Canceled) }}

這里將自身添加到父節點的過程有必要簡單說明一下:

  • 如果父節點也支持cancel,也就是說其父節點肯定有children成員,那么把新context添加到children里即可;
  • 如果父節點不支持cancel,就繼續向上查詢,直到找到一個支持cancel的節點,把新context添加到children里;
  • 如果所有的父節點均不支持cancel,則啟動一個協程等待父節點結束,然后再把當前context結束。

2.3.5 典型使用案例

一個典型的使用cancel context的例子如下所示:

package mainimport (  "fmt"  "time"  "context")func HandelRequest(ctx context.Context) {  go WriteRedis(ctx)  go WriteDatabase(ctx)  for {    select {    case <-ctx.Done():      fmt.Println("HandelRequest Done.")      return    default:      fmt.Println("HandelRequest running")      time.Sleep(2 * time.Second)    }  }}func WriteRedis(ctx context.Context) {  for {    select {    case <-ctx.Done():      fmt.Println("WriteRedis Done.")      return    default:      fmt.Println("WriteRedis running")      time.Sleep(2 * time.Second)    }  }}func WriteDatabase(ctx context.Context) {  for {    select {    case <-ctx.Done():      fmt.Println("WriteDatabase Done.")      return    default:      fmt.Println("WriteDatabase running")      time.Sleep(2 * time.Second)    }  }}func main() {  ctx, cancel := context.WithCancel(context.Background())  go HandelRequest(ctx)  time.Sleep(5 * time.Second)  fmt.Println("It's time to stop all sub goroutines!")  cancel()  //Just for test whether sub goroutines exit or not  time.Sleep(5 * time.Second)}

上面代碼中協程HandelRequest()用于處理某個請求,其又會創建兩個協程:WriteRedis()、WriteDatabase(),main協程創建創建context,并把context在各子協程間傳遞,main協程在適當的時機可以cancel掉所有子協程。

程序輸出如下所示:

HandelRequest runningWriteDatabase runningWriteRedis runningHandelRequest runningWriteDatabase runningWriteRedis runningHandelRequest runningWriteDatabase runningWriteRedis runningIt's time to stop all sub goroutines!WriteDatabase Done.HandelRequest Done.WriteRedis Done.

2.4 timerCtx

源碼包中src/context/context.go:timerCtx 定義了該類型context:

type timerCtx struct {	cancelCtx	timer *time.Timer // Under cancelCtx.mu.	deadline time.Time}

timerCtx在cancelCtx基礎上增加了deadline用于標示自動cancel的最終時間,而timer就是一個觸發自動cancel的定時器。

由此,衍生出WithDeadline()和WithTimeout()。實現上這兩種類型實現原理一樣,只不過使用語境不一樣:

  • deadline: 指定最后期限,比如context將2018.10.20 00:00:00之時自動結束
  • timeout: 指定最長存活時間,比如context將在30s后結束。

對于接口來說,timerCtx在cancelCtx基礎上還需要實現Deadline()和cancel()方法,其中cancel()方法是重寫的。

2.4.1 Deadline()接口實現

Deadline()方法僅僅是返回timerCtx.deadline而矣。而timerCtx.deadline是WithDeadline()或WithTimeout()方法設置的。

2.4.2 cancel()接口實現

cancel()方法基本繼承cancelCtx,只需要額外把timer關閉。

timerCtx被關閉后,timerCtx.cancelCtx.err將會存儲關閉原因:

  • 如果deadline到來之前手動關閉,則關閉原因與cancelCtx顯示一致;
  • 如果deadline到來時自動關閉,則原因為:"context deadline exceeded"

2.4.3 WithDeadline()方法實現

WithDeadline()方法實現步驟如下:

  • 初始化一個timerCtx實例
  • 將timerCtx實例添加到其父節點的children中(如果父節點也可以被cancel的話)
  • 啟動定時器,定時器到期后會自動cancel本context
  • 返回timerCtx實例和cancel()方法

也就是說,timerCtx類型的context不僅支持手動cancel,也會在定時器到來后自動cancel。

2.4.4 WithTimeout()方法實現

WithTimeout()實際調用了WithDeadline,二者實現原理一致。

看代碼會非常清晰:

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

2.4.5 典型使用案例

下面例子中使用WithTimeout()獲得一個context并在其了協程中傳遞:

package mainimport (  "fmt"  "time"  "context")func HandelRequest(ctx context.Context) {  go WriteRedis(ctx)  go WriteDatabase(ctx)  for {    select {    case <-ctx.Done():      fmt.Println("HandelRequest Done.")      return    default:      fmt.Println("HandelRequest running")      time.Sleep(2 * time.Second)    }  }}func WriteRedis(ctx context.Context) {  for {    select {    case <-ctx.Done():      fmt.Println("WriteRedis Done.")      return    default:      fmt.Println("WriteRedis running")      time.Sleep(2 * time.Second)    }  }}func WriteDatabase(ctx context.Context) {  for {    select {    case <-ctx.Done():      fmt.Println("WriteDatabase Done.")      return    default:      fmt.Println("WriteDatabase running")      time.Sleep(2 * time.Second)    }  }}func main() {  ctx, _ := context.WithTimeout(context.Background(), 5 * time.Second)  go HandelRequest(ctx)  time.Sleep(10 * time.Second)}

主協程中創建一個10s超時的context,并將其傳遞給子協程,10s自動關閉context。程序輸出如下:

HandelRequest runningWriteRedis runningWriteDatabase runningHandelRequest runningWriteRedis runningWriteDatabase runningHandelRequest runningWriteRedis runningWriteDatabase runningHandelRequest Done.WriteDatabase Done.WriteRedis Done.

2.5 valueCtx

源碼包中src/context/context.go:valueCtx 定義了該類型context:

type valueCtx struct {	Context	key, val interface{}}

valueCtx只是在Context基礎上增加了一個key-value對,用于在各級協程間傳遞一些數據。

由于valueCtx既不需要cancel,也不需要deadline,那么只需要實現Value()接口即可。

2.5.1 Value()接口實現

由valueCtx數據結構定義可見,valueCtx.key和valueCtx.val分別代表其key和value值。 實現也很簡單:

func (c *valueCtx) Value(key interface{}) interface{} {	if c.key == key {		return c.val	}	return c.Context.Value(key)}

這里有個細節需要關注一下,即當前context查找不到key時,會向父節點查找,如果查詢不到則最終返回interface{}。也就是說,可以通過子context查詢到父的value值。

2.5.2 WithValue()方法實現

WithValue()實現也是非常的簡單, 偽代碼如下:

func WithValue(parent Context, key, val interface{}) Context {	if key == nil {		panic("nil key")	}	return &valueCtx{parent, key, val}}

2.5.3 典型使用案例

下面示例程序展示valueCtx的用法:

package mainimport (  "fmt"  "time"  "context")func HandelRequest(ctx context.Context) {  for {    select {    case <-ctx.Done():      fmt.Println("HandelRequest Done.")      return    default:      fmt.Println("HandelRequest running, parameter: ", ctx.Value("parameter"))      time.Sleep(2 * time.Second)    }  }}func main() {  ctx := context.WithValue(context.Background(), "parameter", "1")  go HandelRequest(ctx)  time.Sleep(10 * time.Second)}

上例main()中通過WithValue()方法獲得一個context,需要指定一個父context、key和value。然后通將該context傳遞給子協程HandelRequest,子協程可以讀取到context的key-value。

注意:本例中子協程無法自動結束,因為context是不支持cancle的,也就是說<-ctx.Done()永遠無法返回。如果需要返回,需要在創建context時指定一個可以cancel的context作為父節點,使用父節點的cancel()在適當的時機結束整個context。

總結

Context僅僅是一個接口定義,跟據實現的不同,可以衍生出不同的context類型;

cancelCtx實現了Context接口,通過WithCancel()創建cancelCtx實例;

timerCtx實現了Context接口,通過WithDeadline()和WithTimeout()創建timerCtx實例;

valueCtx實現了Context接口,通過WithValue()創建valueCtx實例;

三種context實例可互為父節點,從而可以組合成不同的應用形式;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产成人精品av| 欧美激情视频在线观看| 亚洲欧美综合精品久久成人| 日韩视频免费中文字幕| 91精品国产乱码久久久久久久久| 91产国在线观看动作片喷水| 高清视频欧美一级| 57pao精品| 久久免费视频网站| 亚洲视频专区在线| 国产精品9999| 久久久精品美女| 成人写真视频福利网| 久久91亚洲人成电影网站| 色综久久综合桃花网| 51久久精品夜色国产麻豆| 人人爽久久涩噜噜噜网站| 国产一区二区美女视频| 欧美性猛交xxxx黑人猛交| 国产91av在线| 亚洲色图美腿丝袜| 一本色道久久综合狠狠躁篇怎么玩| 亚洲一区二区中文| 亚洲永久免费观看| 91精品国产色综合久久不卡98口| 久久精品成人动漫| 欧美成人激情视频免费观看| 国产成人亚洲综合91精品| 精品久久久久久中文字幕| 国产精品极品在线| 亚洲人永久免费| 亚洲综合国产精品| 日韩av影视综合网| 国产精品视频一区国模私拍| 国产亚洲精品久久久| 91国内免费在线视频| 国产成人av在线播放| 欧美电影免费观看高清完整| 国产精品久久久久久久久影视| xxx欧美精品| 裸体女人亚洲精品一区| 91在线免费网站| 日本aⅴ大伊香蕉精品视频| www.欧美精品一二三区| 国产精品jvid在线观看蜜臀| 亚洲毛片在线观看.| 日本高清久久天堂| 欧美日韩精品在线视频| 中文字幕av一区二区| 97av视频在线| 亚洲国产精品小视频| 欧美中文字幕在线播放| 91视频免费网站| 欧美激情伊人电影| 国产精品普通话| 久久久久久国产精品三级玉女聊斋| 国产精品第一视频| 国产美女91呻吟求| 久久这里只有精品99| 91久久精品久久国产性色也91| 久久视频在线直播| 91精品啪aⅴ在线观看国产| 亚洲自拍在线观看| 精品国产一区二区三区久久狼黑人| 国产aaa精品| 亚洲一区二区在线播放| 欧美又大粗又爽又黄大片视频| 欧美亚洲在线视频| 亚洲free嫩bbb| 成人黄色激情网| 亚洲最新视频在线| 亚洲国产99精品国自产| 国产精品福利无圣光在线一区| 国产精彩精品视频| 日韩人在线观看| 欧美亚洲一区在线| 欧美黄色片在线观看| 国产精品专区第二| 亚洲欧美激情精品一区二区| 亚洲成人激情小说| 欧美午夜影院在线视频| 国产一区二区三区免费视频| 欧美高清视频一区二区| 亚洲天堂av在线免费观看| 亚洲精品网站在线播放gif| 日韩国产高清视频在线| 国产日韩中文在线| 久久中文字幕一区| 欧美国产日韩xxxxx| 亚洲在线观看视频网站| 国产精品久久久av| 久久伊人91精品综合网站| 亚洲社区在线观看| 亚洲国产天堂久久综合| 91经典在线视频| 亚洲第一网站免费视频| 久久综合电影一区| 精品久久久久久久久久ntr影视| 久久婷婷国产麻豆91天堂| 亚洲欧美日韩国产中文专区| 亚洲成人av片| 亚洲影院色在线观看免费| 欧美日韩精品在线播放| 人人做人人澡人人爽欧美| 日韩精品在线视频| 在线亚洲午夜片av大片| 日本成人在线视频网址| 在线性视频日韩欧美| 91精品中国老女人| 欧美激情18p| 亚洲性无码av在线| 午夜精品国产精品大乳美女| 高潮白浆女日韩av免费看| 高清日韩电视剧大全免费播放在线观看| 亚洲欧美综合区自拍另类| 成人激情在线播放| 久久精品国产2020观看福利| 国产欧美久久久久久| 亚洲精品久久7777777| 亚洲国产成人久久综合一区| 欧美成人剧情片在线观看| 久久成人在线视频| 亚洲国产精品久久久久秋霞蜜臀| 国自产精品手机在线观看视频| 一本一本久久a久久精品综合小说| 欧美一乱一性一交一视频| 欧美国产日产韩国视频| 亚洲欧美999| 国产视频精品一区二区三区| 国产亚洲成精品久久| 国产精品中文在线| 亚洲欧洲在线视频| 久久躁狠狠躁夜夜爽| 国产成人精品免高潮费视频| 欧美在线观看www| 国产精品专区第二| 国产精品美腿一区在线看| 亚洲国产三级网| 亚洲国产福利在线| 亚洲大胆美女视频| 在线观看欧美www| 国产欧美韩国高清| 国产精品久久色| 欧美成年人视频网站欧美| 亚洲精品久久久一区二区三区| 欧美国产日本高清在线| 亚洲电影免费观看高清完整版在线| 成人在线中文字幕| 亚洲精品999| 78色国产精品| 欧美视频二区36p| 国内精品久久久久久中文字幕| 国产视频在线一区二区| 91在线网站视频| 久久综合国产精品台湾中文娱乐网| 欧美孕妇与黑人孕交| 91精品国产高清久久久久久| 中文字幕免费国产精品| 欧美高清无遮挡| 亚洲精品资源美女情侣酒店| 91欧美精品成人综合在线观看| 欧美国产极速在线| 国产91成人在在线播放| 国产日产久久高清欧美一区|