亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go 并發(fā)控制context實(shí)現(xiàn)原理剖析(小結(jié))

 更新時(shí)間:2018年10月26日 11:23:08   作者:戀戀美食  
Golang context是Golang應(yīng)用開發(fā)常用的并發(fā)控制技術(shù),這篇文章主要介紹了Go 并發(fā)控制context實(shí)現(xiàn)原理剖析(小結(jié)),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

1. 前言

Golang context是Golang應(yīng)用開發(fā)常用的并發(fā)控制技術(shù),它與WaitGroup最大的不同點(diǎn)是context對(duì)于派生goroutine有更強(qiáng)的控制力,它可以控制多級(jí)的goroutine。

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

典型的使用場(chǎng)景如下圖所示:

上圖中由于goroutine派生出子goroutine,而子goroutine又繼續(xù)派生新的goroutine,這種情況下使用WaitGroup就不太容易,因?yàn)樽觛oroutine個(gè)數(shù)不容易確定。而使用context就可以很容易實(shí)現(xiàn)。

2. Context實(shí)現(xiàn)原理

context實(shí)際上只定義了接口,凡是實(shí)現(xiàn)該接口的類都可稱為是一種context,官方包中實(shí)現(xiàn)了幾個(gè)常用的context,分別可用于不同的場(chǎng)景。

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{}
}

基礎(chǔ)的context接口只定義了4個(gè)方法,下面分別簡(jiǎn)要說(shuō)明一下:

2.1.1 Deadline()

該方法返回一個(gè)deadline和標(biāo)識(shí)是否已設(shè)置deadline的bool值,如果沒(méi)有設(shè)置deadline,則ok == false,此時(shí)deadline為一個(gè)初始值的time.Time值

2.1.2 Done()

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

當(dāng)context關(guān)閉后,Done()返回一個(gè)被關(guān)閉的管道,關(guān)閉的管理仍然是可讀的,據(jù)此goroutine可以收到關(guān)閉請(qǐng)求;當(dāng)context還未關(guān)閉時(shí),Done()返回nil。

2.1.3 Err()

該方法描述context關(guān)閉的原因。關(guān)閉原因由context實(shí)現(xiàn)控制,不需要用戶設(shè)置。比如Deadline context,關(guān)閉原因可能是因?yàn)閐eadline,也可能提前被主動(dòng)關(guān)閉,那么關(guān)閉原因就會(huì)不同:

  • 因deadline關(guān)閉:“context deadline exceeded”;
  • 因主動(dòng)關(guān)閉: "context canceled"。

當(dāng)context關(guān)閉后,Err()返回context的關(guān)閉原因;當(dāng)context還未關(guān)閉時(shí),Err()返回nil;

2.1.3 Value()

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

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

2.2 空context

context包中定義了一個(gè)空的context, 名為emptyCtx,用于context的根節(jié)點(diǎn),空的context只是簡(jiǎn)單的實(shí)現(xiàn)了Context,本身不包含任何值,僅用于其他context的父節(jié)點(diǎn)。

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

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
}

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

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

context包提供了4個(gè)方法創(chuàng)建不同類型的context,使用這四個(gè)方法時(shí)如果沒(méi)有父context,都需要傳入backgroud,即backgroud作為其父節(jié)點(diǎn):

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

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

context包中各context類型之間的關(guān)系,如下圖所示:

struct cancelCtx、valueCtx、valueCtx都繼承于Context,下面分別介紹這三個(gè)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時(shí)會(huì)把其中的所有child都cancle掉。

cancelCtx與deadline和value無(wú)關(guān),所以只需要實(shí)現(xiàn)Done()和Err()接口外露接口即可。

2.3.1 Done()接口實(shí)現(xiàn)

按照Context定義,Done()接口只需要返回一個(gè)channel即可,對(duì)于cancelCtx來(lái)說(shuō)只需要返回成員變量done即可。

這里直接看下源碼,非常簡(jiǎn)單:

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沒(méi)有指定初始化函數(shù),所以cancelCtx.done可能還未分配,所以需要考慮初始化。
cancelCtx.done會(huì)在context被cancel時(shí)關(guān)閉,所以cancelCtx.done的值一般經(jīng)歷如三個(gè)階段:nil --> chan struct{} --> closed chan。

2.3.2 Err()接口實(shí)現(xiàn)

按照Context定義,Err()只需要返回一個(gè)error告知context被關(guān)閉的原因。對(duì)于cancelCtx來(lái)說(shuō)只需要返回成員變量err即可。

還是直接看下源碼:

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

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

2.3.3 cancel()接口實(shí)現(xiàn)

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

cancel方法實(shí)現(xiàn)偽代碼如下所示:

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
  c.mu.Lock()
	
  c.err = err	           //設(shè)置一個(gè)error,說(shuō)明關(guān)閉原因
  close(c.done)           //將channel關(guān)閉,以此通知派生的context
	
  for child := range c.children {  //遍歷所有children,逐個(gè)調(diào)用cancel方法
    child.cancel(false, err)
  }
  c.children = nil
  c.mu.Unlock()

  if removeFromParent {      //正常情況下,需要將自己從parent刪除
    removeChild(c.Context, c)
  }
}

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

2.3.4 WithCancel()方法實(shí)現(xiàn)

WithCancel()方法作了三件事:

  • 初始化一個(gè)cancelCtx實(shí)例
  • 將cancelCtx實(shí)例添加到其父節(jié)點(diǎn)的children中(如果父節(jié)點(diǎn)也可以被cancel的話)
  • 返回cancelCtx實(shí)例和cancel()方法

其實(shí)現(xiàn)源碼如下所示:

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

這里將自身添加到父節(jié)點(diǎn)的過(guò)程有必要簡(jiǎn)單說(shuō)明一下:

  • 如果父節(jié)點(diǎn)也支持cancel,也就是說(shuō)其父節(jié)點(diǎn)肯定有children成員,那么把新context添加到children里即可;
  • 如果父節(jié)點(diǎn)不支持cancel,就繼續(xù)向上查詢,直到找到一個(gè)支持cancel的節(jié)點(diǎn),把新context添加到children里;
  • 如果所有的父節(jié)點(diǎn)均不支持cancel,則啟動(dòng)一個(gè)協(xié)程等待父節(jié)點(diǎn)結(jié)束,然后再把當(dāng)前context結(jié)束。

2.3.5 典型使用案例

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

package main

import (
  "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)
}

上面代碼中協(xié)程HandelRequest()用于處理某個(gè)請(qǐng)求,其又會(huì)創(chuàng)建兩個(gè)協(xié)程:WriteRedis()、WriteDatabase(),main協(xié)程創(chuàng)建創(chuàng)建context,并把context在各子協(xié)程間傳遞,main協(xié)程在適當(dāng)?shù)臅r(shí)機(jī)可以cancel掉所有子協(xié)程。

程序輸出如下所示:

HandelRequest running
WriteDatabase running
WriteRedis running
HandelRequest running
WriteDatabase running
WriteRedis running
HandelRequest running
WriteDatabase running
WriteRedis running
It'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基礎(chǔ)上增加了deadline用于標(biāo)示自動(dòng)cancel的最終時(shí)間,而timer就是一個(gè)觸發(fā)自動(dòng)cancel的定時(shí)器。

由此,衍生出WithDeadline()和WithTimeout()。實(shí)現(xiàn)上這兩種類型實(shí)現(xiàn)原理一樣,只不過(guò)使用語(yǔ)境不一樣:

  • deadline: 指定最后期限,比如context將2018.10.20 00:00:00之時(shí)自動(dòng)結(jié)束
  • timeout: 指定最長(zhǎng)存活時(shí)間,比如context將在30s后結(jié)束。

對(duì)于接口來(lái)說(shuō),timerCtx在cancelCtx基礎(chǔ)上還需要實(shí)現(xiàn)Deadline()和cancel()方法,其中cancel()方法是重寫的。

2.4.1 Deadline()接口實(shí)現(xiàn)

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

2.4.2 cancel()接口實(shí)現(xiàn)

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

timerCtx被關(guān)閉后,timerCtx.cancelCtx.err將會(huì)存儲(chǔ)關(guān)閉原因:

  • 如果deadline到來(lái)之前手動(dòng)關(guān)閉,則關(guān)閉原因與cancelCtx顯示一致;
  • 如果deadline到來(lái)時(shí)自動(dòng)關(guān)閉,則原因?yàn)椋?context deadline exceeded"

2.4.3 WithDeadline()方法實(shí)現(xiàn)

WithDeadline()方法實(shí)現(xiàn)步驟如下:

  • 初始化一個(gè)timerCtx實(shí)例
  • 將timerCtx實(shí)例添加到其父節(jié)點(diǎn)的children中(如果父節(jié)點(diǎn)也可以被cancel的話)
  • 啟動(dòng)定時(shí)器,定時(shí)器到期后會(huì)自動(dòng)cancel本context
  • 返回timerCtx實(shí)例和cancel()方法

也就是說(shuō),timerCtx類型的context不僅支持手動(dòng)cancel,也會(huì)在定時(shí)器到來(lái)后自動(dòng)cancel。

2.4.4 WithTimeout()方法實(shí)現(xiàn)

WithTimeout()實(shí)際調(diào)用了WithDeadline,二者實(shí)現(xiàn)原理一致。

看代碼會(huì)非常清晰:

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

2.4.5 典型使用案例

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

package main

import (
  "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)
}

主協(xié)程中創(chuàng)建一個(gè)10s超時(shí)的context,并將其傳遞給子協(xié)程,10s自動(dòng)關(guān)閉context。程序輸出如下:

HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest running
WriteRedis running
WriteDatabase running
HandelRequest Done.
WriteDatabase Done.
WriteRedis Done.

2.5 valueCtx

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

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

valueCtx只是在Context基礎(chǔ)上增加了一個(gè)key-value對(duì),用于在各級(jí)協(xié)程間傳遞一些數(shù)據(jù)。

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

2.5.1 Value()接口實(shí)現(xiàn)

由valueCtx數(shù)據(jù)結(jié)構(gòu)定義可見,valueCtx.key和valueCtx.val分別代表其key和value值。 實(shí)現(xiàn)也很簡(jiǎn)單:

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

這里有個(gè)細(xì)節(jié)需要關(guān)注一下,即當(dāng)前context查找不到key時(shí),會(huì)向父節(jié)點(diǎn)查找,如果查詢不到則最終返回interface{}。也就是說(shuō),可以通過(guò)子context查詢到父的value值。

2.5.2 WithValue()方法實(shí)現(xiàn)

WithValue()實(shí)現(xiàn)也是非常的簡(jiǎn)單, 偽代碼如下:

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

2.5.3 典型使用案例

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

package main

import (
  "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()中通過(guò)WithValue()方法獲得一個(gè)context,需要指定一個(gè)父context、key和value。然后通將該context傳遞給子協(xié)程HandelRequest,子協(xié)程可以讀取到context的key-value。

注意:本例中子協(xié)程無(wú)法自動(dòng)結(jié)束,因?yàn)閏ontext是不支持cancle的,也就是說(shuō)<-ctx.Done()永遠(yuǎn)無(wú)法返回。如果需要返回,需要在創(chuàng)建context時(shí)指定一個(gè)可以cancel的context作為父節(jié)點(diǎn),使用父節(jié)點(diǎn)的cancel()在適當(dāng)?shù)臅r(shí)機(jī)結(jié)束整個(gè)context。

總結(jié)

Context僅僅是一個(gè)接口定義,跟據(jù)實(shí)現(xiàn)的不同,可以衍生出不同的context類型;

cancelCtx實(shí)現(xiàn)了Context接口,通過(guò)WithCancel()創(chuàng)建cancelCtx實(shí)例;

timerCtx實(shí)現(xiàn)了Context接口,通過(guò)WithDeadline()和WithTimeout()創(chuàng)建timerCtx實(shí)例;

valueCtx實(shí)現(xiàn)了Context接口,通過(guò)WithValue()創(chuàng)建valueCtx實(shí)例;

三種context實(shí)例可互為父節(jié)點(diǎn),從而可以組合成不同的應(yīng)用形式;

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 從生成CRD到編寫自定義控制器教程示例

    從生成CRD到編寫自定義控制器教程示例

    這篇文章主要為大家介紹了從生成CRD到編寫自定義控制器的教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 詳細(xì)介紹Go語(yǔ)言之?dāng)?shù)組與切片

    詳細(xì)介紹Go語(yǔ)言之?dāng)?shù)組與切片

    這篇文章介紹Go語(yǔ)言之?dāng)?shù)組與切片,數(shù)組是具有相同唯一類型的一組已編號(hào)且長(zhǎng)度固定的數(shù)據(jù)項(xiàng)序列,這種類型可是任意的原始類型如整形、字符串或自定義類型。切片是數(shù)組的一個(gè)引用,因此切片是引用類型,在進(jìn)行傳遞時(shí),遵守引用傳遞的機(jī)制,下面我們就來(lái)詳細(xì)了解一下該內(nèi)容
    2021-10-10
  • Go Java算法之同構(gòu)字符串示例詳解

    Go Java算法之同構(gòu)字符串示例詳解

    這篇文章主要為大家介紹了Go Java算法之同構(gòu)字符串示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹

    golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹

    這篇文章主要介紹了golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Golang常用包使用介紹

    Golang常用包使用介紹

    標(biāo)準(zhǔn)的Go語(yǔ)言代碼庫(kù)中包含了大量的包,并且在安裝Go的時(shí)候多數(shù)會(huì)自動(dòng)安裝到系統(tǒng)中。我們可以在$GOROOT/src/pkg目錄中查看這些包。下面簡(jiǎn)單介紹一些我們開發(fā)中常用的包
    2022-09-09
  • Golang中errgroup的常見誤用詳解

    Golang中errgroup的常見誤用詳解

    errgroup和sync.WaitGroup類似,都可以發(fā)起執(zhí)行并等待一組協(xié)程直到所有協(xié)程運(yùn)行結(jié)束,本文主要為大家整理了一些errgroup的常見誤用,有需要的可以參考下
    2024-01-01
  • Golang 之協(xié)程的用法講解

    Golang 之協(xié)程的用法講解

    這篇文章主要介紹了Golang 之協(xié)程的用法講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)錢包的原理解析

    go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)錢包的原理解析

    這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)錢包的原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Go語(yǔ)言中獲取IP地址的方法詳解

    Go語(yǔ)言中獲取IP地址的方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中獲取IP地址的相關(guān)方法,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • 深入理解Golang?make和new的區(qū)別及實(shí)現(xiàn)原理

    深入理解Golang?make和new的區(qū)別及實(shí)現(xiàn)原理

    在Go語(yǔ)言中,有兩個(gè)比較雷同的內(nèi)置函數(shù),分別是new和make方法,二者都可以用來(lái)分配內(nèi)存,那他們有什么區(qū)別呢?下面我們就從底層來(lái)分析一下二者的不同。感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助
    2022-10-10

最新評(píng)論