欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言并發(fā)之context標準庫的使用詳解

 更新時間:2023年06月16日 10:57:26   作者:242030  
Context的出現(xiàn)是為了解決在大型應用程序中的并發(fā)環(huán)境下,協(xié)調(diào)和管理多個goroutine之間的通信、超時和取消操作的問題,本文就來和大家簡單聊聊它的具體用法,希望對大家有所幫助

1、Go語言并發(fā)之context標準庫

Go中的 goroutine 之間沒有父與子的關系,也就沒有所謂子進程退出后的通知機制,多個 goroutine 都是平行地被調(diào)度,多個 goroutine 如何協(xié)作工作涉及通信、同步、通知和退出四個方面。

  • 通信:chan 通道當然是 goroutine 之間通信的基礎,注意這里的通信主要是指程序的數(shù)據(jù)通道。
  • 同步:不帶緩沖的 chan 提供了一個天然的同步等待機制;當然 sync.WaitGroup 也為多個 goroutine 協(xié)同工作提供一種同步等待機制。
  • 通知:這個通知和上面通信的數(shù)據(jù)不一樣,通知通常不是業(yè)務數(shù)據(jù),而是管理、控制流數(shù)據(jù)。要處理這個也好辦,在輸入端綁定兩個 chan,一個用于業(yè)務流數(shù)據(jù),另一個用于異常通知數(shù)據(jù),然后通過 select 收斂進行處理。這個方案可以解決簡單的問題,但不是一個通用的解決方案。
  • 退出:goroutine 之間沒有父子關系,如何通知 goroutine 退出?可以通過增加一個單獨的通道,借助通道和select 的廣播機制實現(xiàn)退出。

Go語言在語法上處理某個 goroutine 退出通知機制很簡單。但是遇到復雜的并發(fā)結構處理起來就顯得力不從心。

實際編程中 goroutine 會拉起新的 goroutine,新的 goroutine 又會拉起另一個新的 goroutine,最終形成一個樹狀的結構,由于 goroutine 里并沒有父子的概念,這個樹狀的結構只是在程序員頭腦中抽象出來的,程序的執(zhí)行模型并沒有維護這么一個樹狀結構。怎么通知這個樹狀上的所有 goroutine 退出?僅依靠語法層面的支持顯然比較難處理。為此 Go1.7 提供了一個標準庫 context 來解決這個問題。它提供兩種功能:退出通知和元數(shù)據(jù)傳遞。

context 庫的設計目的就是跟蹤 goroutine 調(diào)用,在其內(nèi)部維護一個調(diào)用樹,并在這些調(diào)用樹中傳遞通知和元數(shù)據(jù)。

1.1 Context的設計目的

context 庫的設計目的就是跟蹤 goroutine 調(diào)用樹,并在這些 gouroutine 調(diào)用樹中傳遞通知和元數(shù)據(jù)。兩個目的:

(1)、退出通知機制:通知可以傳遞給整個goroutine 調(diào)用樹上的每一個goroutine。

(2)、傳遞數(shù)據(jù):數(shù)據(jù)可以傳遞給整個goroutine 調(diào)用樹上的每一個goroutine。

1.2 基本數(shù)據(jù)結構

在介紹 context 包之前,先理解context包的整體工作機制:第一個創(chuàng)建Context的goroutine被稱為root節(jié)點。

root節(jié)點負責創(chuàng)建一個實現(xiàn)Context接口的具體對象,并將該對象作為參數(shù)傳遞到其新拉起的goroutine,下游的goroutine可以繼續(xù)封裝該對象,再傳遞到更下游的goroutine。

Context 對象在傳遞的過程中最終形成一個樹狀的數(shù)據(jù)結構,這樣通過位干root節(jié)點(樹的根節(jié)點)的Context 對象就能遍歷整個Context 對象樹,通知和消息就可以通過root節(jié)點傳遞出去,實現(xiàn)了上游 goroutine 對下游goroutine的消息傳遞。

1.2.1 Context接口

Context 是一個基本接口,所有的 Context 對象都要實現(xiàn)該接口,context 的使用者在調(diào)用接口中都使用 Context作為參數(shù)類型。

type Context interface{
    // 如果Context實現(xiàn)了超時控制,則該方法返回ok true,deadline為超時時間,否則ok為false
    Deadline() (deadline time.Time,ok bool)
    // 后端被調(diào)的goroutine應該監(jiān)聽該方法返回的chan,以便及時釋放資源
    Done() <-chan struct{}
    // Done返回的chan收到通知的時候,才可以訪問Err()獲知因為什么原因被取消
    Err()error
    // 可以訪問上游 goroutine 傳遞給下游 goroutine 的值
    Value(key interface{}) interface{}
}

1.2.2 canceler接口

canceler 接口是一個擴展接口,規(guī)定了取消通知的 Context 具體類型需要實現(xiàn)的接口。

context 包中的具體類型 cancelctx 和 timerCtx 都實現(xiàn)了該接口。

1.2.3 empty Context結構

emptyCtx 實現(xiàn)了 Context 接口,但不具備任何功能,因為其所有的方法都是空實現(xiàn)。其存在的目的是作為Context 對象樹的根(root 節(jié)點)。因為context包的使用思路就是不停地調(diào)用context 包提供的包裝函數(shù)來創(chuàng)建具有特殊功能的Context 實例,每一個Context 實例的創(chuàng)建都以上一個Context對象為參數(shù),最終形成一個樹狀的結構。

1.2.4 cancelCtx

cancelCtx 是一個實現(xiàn)了Context接口的具體類型,同時實現(xiàn)了conceler接口。conceler具有退出通知方法。注意退出通知機制不但能通知己,也能逐層通知其children 節(jié)點。

1.2.5 timerCtx

timerCtx 是一個實現(xiàn)了Context接口的具體類型,內(nèi)部封裝了cancelCtx類型實例,同時有一個 deadline 變量,用來實現(xiàn)定時退出通知。

1.2.6 valueCtx

valueCtx 是一個實現(xiàn)了Context接口的具體類型,內(nèi)部封裝了Context接口類型,同時封裝了一個k/v的存儲變量,valueCtx 可用來傳遞通知信息。

1.3 API函數(shù)

下面這兩個函數(shù)是構造 Context 取消樹的根節(jié)點對象,根節(jié)點對象用作后續(xù) With 包裝函數(shù)的實參。

func Background() Context
func TODO() Context

With 包裝函數(shù)用來構建不同功能的 Context 具體對象。

(1)、創(chuàng)建一個帶有退出通知的 Context 具體對象,內(nèi)部創(chuàng)建一個 cancelCtx 的類型實例。

func WithCancel(parent Context)(Context,CancelFunc)

(2)、創(chuàng)建一個帶有超時通知的 Context 具體對象,內(nèi)部創(chuàng)建一個 timerCtx 的類型實例。

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

(3)、創(chuàng)建一個帶有超時通知的 Context 具體對象,內(nèi)部創(chuàng)建一個 timerCtx 的類型實例。

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

(4)、創(chuàng)建一個能夠傳遞數(shù)據(jù)的 Context 具體對象,內(nèi)部創(chuàng)建一個 valueCtx 的類型實例。

func WithValue(parent Context,key,val interface{})Context

這些函數(shù)都有一個共同的特點—— parent 參數(shù),其實這就是實現(xiàn) Context 通知樹的必備條件。在 goroutine 的調(diào)用鏈中,Context 的實例被逐層地包裝并傳遞,每層又可以對傳講來的 Context 實例再封裝自己所需的功能,整個調(diào)用樹需要一個數(shù)據(jù)結構來維護,這個維護邏輯在這些包裝函數(shù)內(nèi)部實現(xiàn)。

1.4 輔助函數(shù)

前面描述的 With 開頭的構造函數(shù)是給外部程序使用的 API 接口函數(shù)。Context 具體對象的鏈條關系是在 With 函數(shù)的內(nèi)部維護的。

func propagateCancel(parent Context,child canceler)

1.5 context的用法

package main
import (
	"context"
	"fmt"
	"time"
)
// 定義一個新的類型包含Context字段
type otherContext struct {
	context.Context
}
func main() {
	// 創(chuàng)建一個帶有退出通知的Context具體對象:*cancelCtx
	ctxA, cancel := context.WithCancel(context.Background())
	go work(ctxA, "work1")
	// 超時3秒計算
	tm := time.Now().Add(3 * time.Second)
	// 創(chuàng)建一個帶有超時通知的Context具體對象:*timerCtx
	ctxB, _ := context.WithDeadline(ctxA, tm)
	go work(ctxB, "work2")
	oc := otherContext{ctxB}
	// 創(chuàng)建一個能夠傳遞數(shù)據(jù)的Context具體對象:*cancelCtx
	ctxC := context.WithValue(oc, "key", "god andes,pass from main ")
	go workWithValue(ctxC, "work3")
	time.Sleep(10 * time.Second)
	cancel()
	// 這5秒沒有什么作用
	time.Sleep(5 * time.Second)
	fmt.Println("main stop")
}
// 工作
func work(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("%s get msg to cancel\n", name)
			return
		default:
			fmt.Printf("%s is running \n", name)
			time.Sleep(1 * time.Second)
		}
	}
}
// 根據(jù)context傳遞值
func workWithValue(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("%s get msg to cancel\n", name)
			return
		default:
			value := ctx.Value("key").(string)
			fmt.Printf("%s is running value=%s \n", name, value)
			time.Sleep(1 * time.Second)
		}
	}
}

程序輸出

work3 is running value=god andes,pass from main
work1 is running
work2 is running
work1 is running
work3 is running value=god andes,pass from main
work2 is running
work3 is running value=god andes,pass from main
work2 is running
work1 is running
work1 is running
work3 get msg to cancel
work2 get msg to cancel
work1 is running
work1 is running
work1 is running
work1 is running
work1 is running
work1 is running
work1 get msg to cancel
main stop

上面的輸出中:

work1 is running會輸出10次,因為會休眠10秒。

work2 is running會輸出3次,因為超時時間為3秒。

work3 is running也會輸出3次,因為它的context取決于work2的context。

在使用 Context 的過程中,程序在底層實際上維護了兩條關系鏈,理解這個關系鏈對理解 context 包非常有好處,兩條引用關系鏈如下。

(1)、children key 構成從根到葉子 Context 實例的引用關系,這個關系在調(diào)用 With 函數(shù)時進行維護(調(diào)用上文介紹的 propagateCancel(parent Context,child canceler) 函數(shù)維護),程序有一層這樣的樹狀結構(本示例是一個鏈表結構):

ctxa.children--->ctxb
ctxb.children--->ctxc

這個樹提供一種從根節(jié)點開始遍歷樹的方法,context 包的取消廣播通知的核心就是基于這一點實現(xiàn)的。取消通知沿著這條鏈從根節(jié)點向下層節(jié)點逐層廣播。當然也可以在任意一個子樹上調(diào)用取消通知,一樣會擴散到整棵樹。示例程序中 ctxa 收到退出通知,會通知其綁定 work1,同時會廣播給 ctxb 和 ctxc 綁定的 work2 和 work3。同理,ctxb 收到退出通知,會通知到其綁定的 work2,同時會廣播給 ctxc 綁定的 work3。

(2)、在構造 Context 的對象中不斷地包裹 Context 實例形成一個引用關系鏈,這個關系鏈的方向是相反的,是自底向上的。示例程序中多個 Context 對象的關系如下:

# 自底向上
ctxc.Context -->oc
ctxc.Context.Context -->ctxb
ctxc.Context.Context.cancelCtx-->ctxa
ctxc.Context.Context.cancelCtx.Context-->new(emptyCtx)//context.Background()

這個關系鏈主要用來切斷當前 Context 實例和上層的 Context 實例之間的關系,比如 ctxb 調(diào)用了退出通知或定時器到期了,ctxb 后續(xù)就沒有必要在通知廣播樹上繼續(xù)存在,它需要找到自己的 parent,然后執(zhí)行delete(parent.children,ctxb),把自己從廣播樹上清理掉。

整個關系鏈如圖所示:

通過上文示例梳理出使用 Context 包的一般流程如下:

(1)、創(chuàng)建一個 Context 根對象。

func Background() Context
func TODO() Context

(2)、包裝上一步創(chuàng)建的 Context 對象,使其具有特定的功能。

這些包裝函數(shù)是 context package 的核心,幾乎所有的封裝都是從包裝函數(shù)開始的。原因很簡單,使用 context包的核心就是使用其退出通知廣播功能。

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

(3)、將上一步創(chuàng)建的對象作為實參傳給后續(xù)啟動的并發(fā)函數(shù)(通常作為函數(shù)的第一個參數(shù)),每個并發(fā)函數(shù)內(nèi)部可以繼續(xù)使用包裝函數(shù)對傳進來的 Context 對象進行包裝,添加自己所需的功能。

(4)、頂端的 goroutine 在超時后調(diào)用 cancel 退出通知函數(shù),通知后端的所有 goroutine 釋放資源。

(5)、后端的 goroutine 通過 select 監(jiān)聽 Context.Done() 返回的 chan,及時響應前端 goroutine 的退出通知,一般停止本次處理,釋放所占用的資源。

1.6 使用context傳遞數(shù)據(jù)的爭議

該不該使用context傳遞數(shù)據(jù):

首先要清楚使用 context 包主要是解決 goroutine 的通知退出,傳遞數(shù)據(jù)是其一個額外功能??梢允褂盟鼈鬟f一些元信息,總之使用 context 傳遞的信息不能影響正常的業(yè)務流程,程序不要期待在 context 中傳遞一些必需的參數(shù)等,沒有這些參數(shù),程序也應該能正常工作。

在context中傳遞數(shù)據(jù)的壞處:

(1)、傳遞的都是 interface{} 類型的值,編譯器不能進行嚴格的類型校驗。

(2)、從 interface{} 到具體類型需要使用類型斷言和接口查詢,有一定的運行期開銷和性能損失。

(3)、值在傳遞過程中有可能被后續(xù)的服務覆蓋,且不易被發(fā)現(xiàn)。

(4)、傳遞信息不簡明,較晦澀;不能通過代碼或文檔一眼看到傳遞的是什么,不利于后續(xù)維護。

context應該傳遞什么數(shù)據(jù):

(1)、日志信息。

(2)、調(diào)試信息。

(3)、不影響業(yè)務主邏輯的可選數(shù)據(jù)。

context 包提供的核心的功能是多個 goroutine 之間的退出通知機制,傳遞數(shù)據(jù)只是一個輔助功能,應謹慎使用context 傳遞數(shù)據(jù)。

1.7 Context常用案例

1.7.1 主協(xié)程主動調(diào)用cancel()取消子context

package main
import (
	"context"
	"fmt"
	"time"
)
func handler() {
	fmt.Println("handler start...", time.Now())
	ctx, cancel := context.WithCancel(context.Background())
	go do(ctx)
	// 子孫攜程中的ctx會被取消
	cancel()
	time.Sleep(5 * time.Second)
	fmt.Println("handler end...", time.Now())
}
func do(ctx context.Context) {
	i := 1
	for {
		time.Sleep(1 * time.Second)
		select {
		case <-ctx.Done():
			fmt.Println("done", time.Now())
			return
		default:
			fmt.Printf("work %d seconds: %v\n", i, time.Now())
		}
		i++
	}
}
func main() {
	fmt.Println("main start...")
	handler()
	fmt.Println("main end...")
}

程序輸出

main start...
handler start... 2023-02-06 12:18:29.3556787 +0800 CST m=+0.002016401
done 2023-02-06 12:18:30.3665436 +0800 CST m=+1.012881301
handler end... 2023-02-06 12:18:34.365822 +0800 CST m=+5.012159701
main end...

通過輸出我們可以看出來,在主協(xié)程調(diào)用了 cancel() 之后,子協(xié)程中的 ctx 會被主動關閉掉,延遲時間是1秒,會看到打印 done。

package main
import (
	"context"
	"fmt"
	"time"
)
func handler() {
	fmt.Println("handler start...", time.Now())
	ctx, _ := context.WithCancel(context.Background())
	go do(ctx)
	// 子孫攜程中的ctx會被取消
	time.Sleep(5 * time.Second)
	fmt.Println("handler end...", time.Now())
}
func do(ctx context.Context) {
	i := 1
	for {
		time.Sleep(1 * time.Second)
		select {
		case <-ctx.Done():
			fmt.Println("done", time.Now())
			return
		default:
			fmt.Printf("work %d seconds: %v\n", i, time.Now())
		}
		i++
	}
}
func main() {
	fmt.Println("main start...")
	handler()
	fmt.Println("main end...")
}

程序輸出

main start...
handler start... 2023-06-10 10:49:42.9315016 +0800 CST m=+0.002174101
work 1 seconds: 2023-06-10 10:49:43.9404127 +0800 CST m=+1.011085201
work 2 seconds: 2023-06-10 10:49:44.941524 +0800 CST m=+2.012196501
work 3 seconds: 2023-06-10 10:49:45.9422753 +0800 CST m=+3.012947801
work 4 seconds: 2023-06-10 10:49:46.9426627 +0800 CST m=+4.013335201
handler end... 2023-06-10 10:49:47.9421403 +0800 CST m=+5.012812801
main end...

從結果看出,我們?nèi)绻粓?zhí)行 cancel(),則會在設置的5秒睡眠時間內(nèi)執(zhí)行 work。

1.7.2 超時之后,調(diào)用cancle()

package main
import (
	"context"
	"fmt"
	"time"
)
func handler() {
	fmt.Println("handler start...", time.Now())
	// 設置2秒的超時時間
	ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
	go do(ctx)
	time.Sleep(5 * time.Second)
	fmt.Println("handler end...", time.Now())
}
func do(ctx context.Context) {
	i := 1
	for {
		time.Sleep(1 * time.Second)
		select {
		case <-ctx.Done():
			fmt.Println("done", time.Now())
			return
		default:
			fmt.Printf("work %d seconds: %v\n", i, time.Now())
		}
		i++
	}
}
func main() {
	fmt.Println("main start...")
	handler()
	fmt.Println("main end...")
}

程序輸出

main start...
handler start... 2023-06-10 10:51:17.0507657 +0800 CST m=+0.002568201
work 1 seconds: 2023-06-10 10:51:18.0618317 +0800 CST m=+1.013634201
done 2023-06-10 10:51:19.0620425 +0800 CST m=+2.013845001
handler end... 2023-06-10 10:51:22.0630702 +0800 CST m=+5.014872701
main end...

通過輸出可以看出來,在2s超時之后,也就是done會主動打印出來,表明 cancel() 被主動調(diào)用了。

以上就是Go語言并發(fā)之context標準庫的使用詳解的詳細內(nèi)容,更多關于Go語言context庫的資料請關注腳本之家其它相關文章!

相關文章

  • Golang在圖像中繪制矩形框的示例代碼

    Golang在圖像中繪制矩形框的示例代碼

    這篇文章主要介紹了Golang在圖像中繪制矩形框的示例代碼,文中有詳細的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下
    2008-08-08
  • 使用Go語言開發(fā)自動化API測試工具詳解

    使用Go語言開發(fā)自動化API測試工具詳解

    這篇文章主要為大家詳細介紹了如何使用Go語言開發(fā)自動化API測試工具,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下
    2024-03-03
  • 解決Golang小數(shù)float64在實際工程中加減乘除的精度問題

    解決Golang小數(shù)float64在實際工程中加減乘除的精度問題

    這篇文章主要介紹了解決Golang小數(shù)float64在實際工程中加減乘除的精度問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Go語言中sync包使用方法教程

    Go語言中sync包使用方法教程

    在Go語言的并發(fā)編程實踐中,性能優(yōu)化總是繞不開的話題,下面這篇文章主要介紹了Go語言中sync包使用方法的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-03-03
  • Go語言使用defer+recover解決panic導致程序崩潰的問題

    Go語言使用defer+recover解決panic導致程序崩潰的問題

    如果協(xié)程出現(xiàn)了panic,就會造成程序的崩潰,這時可以在goroutine中使用recover來捕獲panic,進行處理,本文就詳細的介紹一下,感興趣的可以了解一下
    2021-09-09
  • golang網(wǎng)絡socket粘包問題的解決方法

    golang網(wǎng)絡socket粘包問題的解決方法

    這篇文章主要介紹了golang網(wǎng)絡socket粘包問題的解決方法,簡單講述了socket粘包的定義并結合實例形式分析了Go語言解決粘包問題的方法,需要的朋友可以參考下
    2016-07-07
  • golang簡單獲取上傳文件大小的實現(xiàn)代碼

    golang簡單獲取上傳文件大小的實現(xiàn)代碼

    這篇文章主要介紹了golang簡單獲取上傳文件大小的方法,涉及Go語言文件傳輸及文件屬性操作的相關技巧,需要的朋友可以參考下
    2016-07-07
  • 解決GOPATH在GOLAND中的坑

    解決GOPATH在GOLAND中的坑

    這篇文章主要介紹了解決GOPATH在GOLAND中的坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang?range?slice?與range?array?之間的區(qū)別

    Golang?range?slice?與range?array?之間的區(qū)別

    這篇文章主要介紹了Golang?range?slice?與range?array?之間的區(qū)別,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-07-07
  • 深入了解Golang官方container/list原理

    深入了解Golang官方container/list原理

    在?Golang?的標準庫?container?中,包含了幾種常見的數(shù)據(jù)結構的實現(xiàn),其實是非常好的學習材料,本文主要為大家介紹了container/list的原理與使用,感興趣的可以了解一下
    2023-08-08

最新評論