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

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

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

Context 背景 和 適用場景

Context 的背景

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

Context 的功能和目的

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

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

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

總體來說,context 的目的主要包括兩個:

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

Context 的基本使用

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

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

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

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

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

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

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

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

Context interface 接口定義

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

定義如下:

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

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

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

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

func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

Background 和 TODO 兩個其實都是基于 emptyCtx 來實現(xiàn)的,emptyCtx 類型實現(xiàn)了 context 接口定義的 4 個方法,它本身是一個不可取消,沒有設(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
}

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

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

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

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

Context 的常用方法實例

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. 通過 context.WithValue 來傳值

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

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

相關(guān)文章

  • Go語言開發(fā)必知的一個內(nèi)存模型細(xì)節(jié)

    Go語言開發(fā)必知的一個內(nèi)存模型細(xì)節(jié)

    這篇文章主要為大家介紹了Go語言開發(fā)必知的一個內(nèi)存模型細(xì)節(jié)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • 詳解Go語言中的內(nèi)存對齊

    詳解Go語言中的內(nèi)存對齊

    前面我們學(xué)習(xí)了Go語言空結(jié)構(gòu)體詳解,最近又在看unsafe包的知識,在查閱相關(guān)資料時不免會看到內(nèi)存對齊相關(guān)的內(nèi)容。雖然不會,但可以學(xué)呀,那么這篇文章,我們就一起來看下什么是內(nèi)存對齊吧
    2022-10-10
  • GoLang中Strconv庫有哪些常用方法

    GoLang中Strconv庫有哪些常用方法

    這篇文章主要介紹了GoLang中Strconv庫有哪些常用方法,strconv庫實現(xiàn)了基本數(shù)據(jù)類型與其字符串表示的轉(zhuǎn)換,主要有以下常用函數(shù):?Atoi()、Itia()、parse系列、format系列、append系列
    2023-01-01
  • Qt6.5 grpc組件使用 + golang grpc server示例詳解

    Qt6.5 grpc組件使用 + golang grpc server

    這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • 淺談golang類型斷言,失敗類型斷言返回值問題

    淺談golang類型斷言,失敗類型斷言返回值問題

    這篇文章主要介紹了淺談golang類型斷言,失敗類型斷言返回值問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 使用go備份StarRocks建表語句方法實例

    使用go備份StarRocks建表語句方法實例

    這篇文章主要為大家介紹了使用go備份StarRocks建表語句方法實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Golang中拼接字符串的6種方式性能對比

    Golang中拼接字符串的6種方式性能對比

    golang的string類型是不可修改的,對于拼接字符串來說,本質(zhì)上還是創(chuàng)建一個新的對象將數(shù)據(jù)放進去,主要有6種拼接方式,下面小編就來為大家詳細(xì)講講吧
    2025-03-03
  • Go語言中break label與goto label的區(qū)別

    Go語言中break label與goto label的區(qū)別

    這篇文章主要介紹了Go語言中break label與goto label的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go語言中如何實現(xiàn)并發(fā)

    Go語言中如何實現(xiàn)并發(fā)

    Go的并發(fā)機制通過協(xié)程和通道的簡單性和高效性,使得編寫并發(fā)代碼變得相對容易,這種并發(fā)模型被廣泛用于構(gòu)建高性能的網(wǎng)絡(luò)服務(wù)、并行處理任務(wù)和其他需要有效利用多核處理器的應(yīng)用程序,這篇文章主要介紹了在Go中如何實現(xiàn)并發(fā),需要的朋友可以參考下
    2023-09-09
  • golang移除切片索引位置的元素的兩種方法

    golang移除切片索引位置的元素的兩種方法

    本文主要介紹了golang移除切片索引位置的元素的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08

最新評論