最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Golang中Context的原理和使用技巧

 更新時(shí)間:2022年11月29日 11:27:12   作者:AllenWu  
Golang?的?Context?包,中文可以稱之為“上下文”,是用來(lái)在?goroutine?協(xié)程之間進(jìn)行上下文信息傳遞的,這些上下文信息包括?kv?數(shù)據(jù)、取消信號(hào)、超時(shí)時(shí)間、截止時(shí)間等。本文主要介紹了Context的原理和使用技巧,希望對(duì)大家有所幫助

Context 背景 和 適用場(chǎng)景

Context 的背景

Golang 在 1.6.2 的時(shí)候還沒(méi)有自己的 context,在1.7的版本中就把 https://pkg.go.dev/golang.org/x/net/context包被加入到了官方的庫(kù)中。Golang 的 Context 包,中文可以稱之為“上下文”,是用來(lái)在 goroutine 協(xié)程之間進(jìn)行上下文信息傳遞的,這些上下文信息包括 kv 數(shù)據(jù)、取消信號(hào)、超時(shí)時(shí)間、截止時(shí)間等。

Context 的功能和目的

雖然我們知道了 context 上下文的基本信息,但是想想,為何 Go 里面把 Context 單獨(dú)擰出來(lái)設(shè)計(jì)呢?這就和 Go 的并發(fā)有比較大的關(guān)系,因?yàn)?Go 里面創(chuàng)建并發(fā)協(xié)程非常容易,但是,如果沒(méi)有相關(guān)的機(jī)制去控制這些這些協(xié)程的生命周期,那么可能導(dǎo)致協(xié)程泛濫,也可能導(dǎo)致請(qǐng)求大量超時(shí),協(xié)程無(wú)法退出導(dǎo)致協(xié)程泄漏、協(xié)程泄漏導(dǎo)致協(xié)程占用的資源無(wú)法釋放,從而導(dǎo)致資源被占滿等各種問(wèn)題。所以,context 出現(xiàn)的目的就是為了解決并發(fā)協(xié)程之間父子進(jìn)程的退出控制。

一個(gè)常見(jiàn)例子,有一個(gè) web 服務(wù)器,來(lái)一個(gè)請(qǐng)求,開(kāi)多個(gè)協(xié)程去處理這個(gè)請(qǐng)求的業(yè)務(wù)邏輯,比如,查詢登錄狀態(tài)、獲取用戶信息、獲取業(yè)務(wù)信息等,那么如果請(qǐng)求的下游協(xié)程的生命周期無(wú)法控制,那么我們的業(yè)務(wù)請(qǐng)求就可能會(huì)一直超時(shí),業(yè)務(wù)服務(wù)可能會(huì)因?yàn)閰f(xié)程沒(méi)有釋放導(dǎo)致協(xié)程泄漏。因此,協(xié)程之間能夠進(jìn)行事件通知并且能控制協(xié)程的生命周期非常重要,怎么實(shí)現(xiàn)呢? context 就是來(lái)干這些事的。

另外,既然有大量并發(fā)協(xié)程,那么各個(gè)協(xié)程之間的一些基礎(chǔ)數(shù)據(jù)如果想要共享,比如把每個(gè)請(qǐng)求鏈路的 tarceID 都進(jìn)行傳遞,這樣把整個(gè)鏈路串起來(lái),要怎么做呢? 還是要依靠 context。

總體來(lái)說(shuō),context 的目的主要包括兩個(gè):

  • 協(xié)程之間的事件通知(超時(shí)、取消)
  • 協(xié)程之間的數(shù)據(jù)傳遞鍵值對(duì)的數(shù)據(jù)(kv 數(shù)據(jù))

Context 的基本使用

Go 語(yǔ)言中的 Context 直接使用官方的 "context" 包就可以開(kāi)始使用了,一般是在我們所有要傳遞的地方(函數(shù)的第一個(gè)參數(shù))把 context.Context 類型的變量傳遞,并對(duì)其進(jìn)行相關(guān) API 的使用。context 常用的使用姿勢(shì)包括但不限于:

  • 通過(guò) context 進(jìn)行數(shù)據(jù)傳遞,但是這里只能傳遞一些通用或者基礎(chǔ)的元數(shù)據(jù),不要傳遞業(yè)務(wù)層面的數(shù)據(jù),不是說(shuō)不可以傳遞,是在 Go 的編碼規(guī)范或者慣用法中不提倡
  • 通過(guò) context 進(jìn)行協(xié)程的超時(shí)控制
  • 通過(guò) context 進(jìn)行并發(fā)控制

Context 的同步控制設(shè)計(jì)

Go 里面控制并發(fā)有兩種經(jīng)典的方式,一種是 WaitGroup,另外一種就是 Context。

在 Go 里面,當(dāng)需要進(jìn)行多批次的計(jì)算任務(wù)同步,或者需要一對(duì)多的協(xié)作流程的時(shí)候;通過(guò) Context 的關(guān)聯(lián)關(guān)系(go 的 context 被設(shè)計(jì)為包含了父子關(guān)系),我們就可以控制子協(xié)程的生命周期,而其他的同步方式是無(wú)法控制其生命周期的,只能是被動(dòng)阻塞等待完成或者結(jié)束。context 控制子協(xié)程的生命周期,是通過(guò) context 的 context.WithTimeout 機(jī)制來(lái)實(shí)現(xiàn)的,這個(gè)是一般系統(tǒng)中或者底層各種框架、庫(kù)的普適用法。context 對(duì)并發(fā)做一些控制包括 Context Done 取消、截止時(shí)間取消 context.WithDeadline、超時(shí)取消 context.WithTimeout 等。

比如有一個(gè)網(wǎng)絡(luò)請(qǐng)求 Request,每個(gè) Request 都需要開(kāi)啟一個(gè) goroutine 做一些業(yè)務(wù)邏輯,這些 goroutine 又可能會(huì)開(kāi)啟其他的 goroutine。那么這樣的話,我們就可以通過(guò) Context 來(lái)跟蹤并控制這些 goroutine。

另外一個(gè)實(shí)際例子是,在 Go 實(shí)現(xiàn)的 web server 中,每個(gè)請(qǐng)求都會(huì)開(kāi)一個(gè) goroutine 去處理。但是我們的這個(gè) goroutine 請(qǐng)求邏輯里面, 還需繼續(xù)創(chuàng)建goroutine 去訪問(wèn)后端其他資源,比如數(shù)據(jù)庫(kù)、RPC 服務(wù)等。由于這些 goroutine 都是在處理同一個(gè)請(qǐng)求,因此,如果請(qǐng)求超時(shí)或者被取消后,所有的 goroutine 都應(yīng)該馬上退出并且釋放相關(guān)的資源,這種情況也需要用 Context 來(lái)為我們?nèi)∠羲?goroutine。

Context 的定義和實(shí)現(xiàn)

Context interface 接口定義

在 golang 里面,interface 是一個(gè)使用非常廣泛的結(jié)構(gòu),它可以接納任何類型。而 context 就是通過(guò) interface 來(lái)定義的,定義很簡(jiǎn)單,一共4個(gè)方法,這也是 Go 的設(shè)計(jì)理念,接口盡量簡(jiǎn)單、小巧,通過(guò)組合來(lái)實(shí)現(xiàn)豐富的功能。

定義如下:

type Context interface {
    //  返回 context 是否會(huì)被取消以及自動(dòng)取消的截止時(shí)間(即 deadline)
    Deadline() (deadline time.Time, ok bool)
    
    // 當(dāng) context 被取消或者到了 deadline,返回一個(gè)被關(guān)閉的 channel
    Done() <-chan struct{}
    
    // 返回取消的錯(cuò)誤原因,因?yàn)槭裁?Context 被取消
    Err() error
    
    // 獲取 key 對(duì)應(yīng)的 value
    Value(key interface{}) interface{}
}
  • Deadline 返回 context 是否會(huì)被取消以及自動(dòng)取消的截止時(shí)間,第一個(gè)返回值是截止時(shí)間,到了這個(gè)時(shí)間點(diǎn),Context 會(huì)自動(dòng)發(fā)起取消請(qǐng)求;第二個(gè)返回值 ok==false 時(shí)表示沒(méi)有設(shè)置截止時(shí)間,如果需要取消的話,需要調(diào)用取消函數(shù)進(jìn)行取消。
  • Done 方法返回一個(gè)只讀的 chan,類型為 struct{},如果該方法返回的 chan 可以讀取,那么就說(shuō)明 parent context 已經(jīng)發(fā)起了取消請(qǐng)求,當(dāng)我們通過(guò) Done 方法收到這個(gè)信號(hào)后,就應(yīng)該做清理操作,然后退出 goroutine,釋放資源。之后,Err 方法會(huì)返回一個(gè)錯(cuò)誤,告知為什么 Context 被取消。
  • Err 方法返回取消的錯(cuò)誤原因,因?yàn)槭裁?Context 被取消。
  • Value 方法獲取該 Context 上保存的鍵值對(duì),所以要通過(guò)一個(gè) Key 才可以獲取對(duì)應(yīng)的值,這個(gè)值一般是線程安全(并發(fā)安全)的。雖然 context 是一個(gè)并發(fā)安全的類型,但是如果 context 中保存著 value,則這些 value 通常不是并發(fā)安全的,并發(fā)讀寫這些 value 可能會(huì)造成數(shù)據(jù)錯(cuò)亂,嚴(yán)重的情況下可能發(fā)生 panic,所以在并發(fā)時(shí),如果我們的業(yè)務(wù)代碼需要讀寫 context 中的 value,那么最好建議我們 clone 一份原來(lái)的 context 中的 value,并塞到新的 ctx 傳遞給各個(gè)gorouinte。當(dāng)然, 如果已經(jīng)明確不會(huì)有并發(fā)讀取,那么可以直接使用,或者使用的時(shí)候加鎖。

parent Context 的具體實(shí)現(xiàn)

Context 雖然是個(gè)接口,但是并不需要使用方實(shí)現(xiàn),golang 內(nèi)置的 context 包,已經(jīng)幫我們實(shí)現(xiàn)了,查看 Go 的源碼可以看到如下定義:

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

func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

Background 和 TODO 兩個(gè)其實(shí)都是基于 emptyCtx 來(lái)實(shí)現(xiàn)的,emptyCtx 類型實(shí)現(xiàn)了 context 接口定義的 4 個(gè)方法,它本身是一個(gè)不可取消,沒(méi)有設(shè)置截止時(shí)間,沒(méi)有攜帶任何值的 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
}

Background 方法,一般是在 main 函數(shù)的入口處(或者請(qǐng)求最初的根 context)就定義并使用,然后一直往下傳遞,接下來(lái)所有的子協(xié)程里面都是基于 main 的 context 來(lái)衍生的。TODO 這個(gè)一般不建議業(yè)務(wù)上使用,一般沒(méi)有實(shí)際意義,在單元測(cè)試?yán)锩婵梢允褂谩?/p>

Context 的繼承和各種 With 系列函數(shù)

查看官方文檔 https://pkg.go.dev/golang.org/x/net/context

// 最基礎(chǔ)的實(shí)現(xiàn),也可以叫做父 context
func Background() Context
func TODO() Context

// 在 Background() 根 context 基礎(chǔ)上派生的各種  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 interface{}, val interface{}) Context
  • WithCancel 函數(shù),傳遞一個(gè) parent Context 作為參數(shù),返回子 Context,以及一個(gè)取消函數(shù)用來(lái)取消 Context。我們前面說(shuō)到控制父子協(xié)程的生命周期,那么就可以通過(guò)這個(gè)函數(shù)來(lái)實(shí)現(xiàn)
  • WithDeadline 函數(shù),和 WithCancel 差不多,但是它會(huì)多傳遞一個(gè)截止時(shí)間參數(shù),這樣的話,當(dāng)?shù)搅私刂沟臅r(shí)間點(diǎn),就會(huì)自動(dòng)取消 Context,當(dāng)然我們也可以不等到這個(gè)時(shí)候,然后可以通過(guò)取消函數(shù)提前進(jìn)行取消。
  • WithTimeout 函數(shù),和 WithDeadline 基本上一樣,會(huì)傳入一個(gè) timeout 超時(shí)時(shí)間,也就是是從現(xiàn)在開(kāi)始,直到過(guò)來(lái) timeout 時(shí)間后,就進(jìn)行超時(shí)取消,注意,這個(gè)是超時(shí)取消,不是截止時(shí)間取消。
  • WithValue 函數(shù),這個(gè)和 WithCancel 就沒(méi)有關(guān)系了,它不是用來(lái)控制父子協(xié)程生命周期的,這個(gè)是我們說(shuō)到的,在 context 中傳遞基礎(chǔ)元數(shù)據(jù)用的,這個(gè)可以在 context 中存儲(chǔ)鍵值對(duì)的數(shù)據(jù),然后這個(gè)鍵值對(duì)的數(shù)據(jù)可以通過(guò) Context.Value 方法獲取到,這是我們實(shí)際用經(jīng)常要用到的技巧,一般我們想要通過(guò)上下文來(lái)傳遞數(shù)據(jù)時(shí),可以通過(guò)這個(gè)方法,如我們需要 tarceID 追蹤系統(tǒng)調(diào)用棧的時(shí)候。

Context 的常用方法實(shí)例

1. 調(diào)用 Context Done方法取消

func ContextDone(ctx context.Context, out chan<- Value) error {

	for {
		v, err := AllenHandler(ctx)

		if err != nil {
			return err
		}
		select {
		case <-ctx.Done():
			log.Infof("context has done")
			return ctx.Err()
		case out <- v:
		}
	}
}

2. 通過(guò) context.WithValue 來(lái)傳值

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	valueCtx := context.WithValue(ctx, key, "add value from allen")

	go watchAndGetValue(valueCtx)

	time.Sleep(10 * time.Second)

	cancel()

	time.Sleep(5 * time.Second)
}

func watchAndGetValue(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			//get value
			log.Infof(ctx.Value(key), "is cancel")

			return
		default:
			//get value
			log.Infof(ctx.Value(key), "int goroutine")

			time.Sleep(2 * time.Second)
		}
	}
}

3. 超時(shí)取消 context.WithTimeout

	package main
	
	import (
		"fmt"
		"sync"
		"time"
	
		"golang.org/x/net/context"
	)
	
	var (
		wg sync.WaitGroup
	)
	
	func work(ctx context.Context) error {
		defer wg.Done()
	
		for i := 0; i < 1000; i++ {
			select {
			case <-time.After(2 * time.Second):
				fmt.Println("Doing some work ", i)
	
			// we received the signal of cancelation in this channel
			case <-ctx.Done():
				fmt.Println("Cancel the context ", i)
				return ctx.Err()
			}
		}
		return nil
	}
	
	func main() {
		ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
		defer cancel()
	
		fmt.Println("Hey, I'm going to do some work")
	
		wg.Add(1)
		go work(ctx)
		wg.Wait()
	
		fmt.Println("Finished. I'm going home")
	}

4. 截止時(shí)間取消 context.WithDeadline

	package main
	
	import (
		"context"
		"fmt"
		"time"
	)
	
	func main() {
		d := time.Now().Add(1 * time.Second)
		ctx, cancel := context.WithDeadline(context.Background(), d)
	
		// Even though ctx will be expired, it is good practice to call its
		// cancelation function in any case. Failure to do so may keep the
		// context and its parent alive longer than necessary.
		defer cancel()
	
		select {
		case <-time.After(2 * time.Second):
			fmt.Println("oversleep")
		case <-ctx.Done():
			fmt.Println(ctx.Err())
		}
	}

Context 使用原則 和 技巧

  • Context 是線程安全的,可以放心的在多個(gè) goroutine 協(xié)程中傳遞
  • 可以把一個(gè) Context 對(duì)象傳遞給任意個(gè)數(shù)的 gorotuine,對(duì)它執(zhí)行 取消 操作時(shí),所有 goroutine 都會(huì)接收到取消信號(hào)。
  • 不要把 Context 放在結(jié)構(gòu)體中,要以參數(shù)的方式傳遞,parent Context 一般為Background,并且一般要在 main 函數(shù)的入口處創(chuàng)建然后傳遞下去
  • Context 的變量名建議都統(tǒng)一為 ctx,并且要把 Context 作為第一個(gè)參數(shù)傳遞給入口請(qǐng)求和出口請(qǐng)求鏈路上的每一個(gè)函數(shù)
  • 往下游給一個(gè)函數(shù)方法傳遞 Context 的時(shí)候,千萬(wàn)不要傳遞 nil,否則在 tarce 追蹤的時(shí)候,就會(huì)中斷鏈路,并且如果函數(shù)里面有獲取值的邏輯,可能導(dǎo)致 panic。
  • Context 的 Value 只能傳遞一些通用或者基礎(chǔ)的元數(shù)據(jù),不要傳遞業(yè)務(wù)層面的數(shù)據(jù),不是說(shuō)不可以傳遞,是在 Go 的編碼規(guī)范或者慣用法中不提倡不要什么數(shù)據(jù)都使用這個(gè)傳遞。由于 context 存儲(chǔ) key-value 是鏈?zhǔn)降模虼瞬樵儚?fù)雜度為O(n),所以,盡量不要隨意存儲(chǔ)不必要的數(shù)據(jù)

到此這篇關(guān)于詳解Golang中Context的原理和使用技巧的文章就介紹到這了,更多相關(guān)Golang Context內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 重學(xué)Go語(yǔ)言之如何開(kāi)發(fā)RPC應(yīng)用

    重學(xué)Go語(yǔ)言之如何開(kāi)發(fā)RPC應(yīng)用

    這篇文章主要為大家詳細(xì)介紹了在Go語(yǔ)言中如何構(gòu)建RPC應(yīng)用,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09
  • Go Gin 處理跨域問(wèn)題解決

    Go Gin 處理跨域問(wèn)題解決

    在前后端分離的項(xiàng)目中,經(jīng)常會(huì)遇到跨域問(wèn)題,本文主要介紹了Go Gin 處理跨域問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • Go底層之http標(biāo)準(zhǔn)庫(kù)服務(wù)端實(shí)現(xiàn)原理分析

    Go底層之http標(biāo)準(zhǔn)庫(kù)服務(wù)端實(shí)現(xiàn)原理分析

    這篇文章主要介紹了Go底層之http標(biāo)準(zhǔn)庫(kù)服務(wù)端實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Go基礎(chǔ)教程系列之Go接口使用詳解

    Go基礎(chǔ)教程系列之Go接口使用詳解

    這篇文章主要介紹了Go基礎(chǔ)教程系列之Go接口使用詳解,需要的朋友可以參考下
    2022-04-04
  • Go語(yǔ)言中的package和go modules的實(shí)現(xiàn)

    Go語(yǔ)言中的package和go modules的實(shí)現(xiàn)

    本文主要介紹了Go語(yǔ)言中的package和go modules的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-07-07
  • Golang中errgroup的常見(jiàn)誤用詳解

    Golang中errgroup的常見(jiàn)誤用詳解

    errgroup和sync.WaitGroup類似,都可以發(fā)起執(zhí)行并等待一組協(xié)程直到所有協(xié)程運(yùn)行結(jié)束,本文主要為大家整理了一些errgroup的常見(jiàn)誤用,有需要的可以參考下
    2024-01-01
  • 帶你在Go?test中體驗(yàn)jest的安裝使用

    帶你在Go?test中體驗(yàn)jest的安裝使用

    這篇文章帶你在Go?test中體驗(yàn)jest的安裝使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go語(yǔ)言非main包編譯為靜態(tài)庫(kù)并使用的示例代碼

    Go語(yǔ)言非main包編譯為靜態(tài)庫(kù)并使用的示例代碼

    本文以Windows為例,介紹一下如何將Go的非main包編譯為靜態(tài)庫(kù),用戶又將如何使用。通過(guò)實(shí)際項(xiàng)目創(chuàng)建常規(guī)工程,通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07
  • Go歸并排序算法的實(shí)現(xiàn)方法

    Go歸并排序算法的實(shí)現(xiàn)方法

    歸并排序采用的也是分治的策略,把原本的問(wèn)題先分解成一些小問(wèn)題進(jìn)行求解,再把這些小問(wèn)題各自的答案修整到一起得到原本問(wèn)題的答案,從而達(dá)到分而治之的目的,對(duì)Go歸并排序算法相關(guān)知識(shí)感興趣的朋友一起看看吧
    2022-04-04
  • Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    這篇文章主要為大家介紹了Go語(yǔ)言處理超大字符串型整數(shù)加減經(jīng)典面試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10

最新評(píng)論

剑河县| 遂川县| 双江| 石门县| 西林县| 西林县| 噶尔县| 普安县| 青河县| 木里| 玛纳斯县| 西峡县| 临汾市| 新巴尔虎右旗| 绍兴县| 南京市| 南京市| 洮南市| 古交市| 宿迁市| 山阳县| 东城区| 全椒县| 四会市| 安塞县| 藁城市| 句容市| 无为县| 岐山县| 襄城县| 石首市| 平谷区| 海南省| 师宗县| 荆州市| 灵寿县| 扶风县| 屯留县| 腾冲县| 普宁市| 青冈县|