重學(xué)Go語言之如何使用Context
我們知道在開發(fā)Go應(yīng)用程序時(shí),尤其是網(wǎng)絡(luò)應(yīng)用程序,需要啟動(dòng)大量的Goroutine
來處理請(qǐng)求:
不過Goroutine
被創(chuàng)建之后,除非執(zhí)行后正常退出或者觸發(fā)panic
退出,Go并沒有提供在一個(gè)Goroutine
中關(guān)閉另一個(gè)Goroutine
的機(jī)制。
有沒有一種方法,可以從一個(gè)Goroutine
中通知另一個(gè)Goroutine
退出執(zhí)行呢?這時(shí)候就該Context
登場(chǎng)了!
什么是Context
Context
,中文叫做上下文
,Go語言在1.7
版本中新增的context
包中定義了Context
,Context
本質(zhì)是一個(gè)接口,這個(gè)接口一共定義了四個(gè)方法:
type?Context?interface?{ ?Deadline()?(deadline?time.Time,?ok?bool) ?Done()?<-chan?struct{} ?Err()?error ?Value(key?any)?any }
Dateline()
:獲取定時(shí)關(guān)閉的時(shí)間。Done()
:從一個(gè)channel
獲取關(guān)閉的信號(hào)Err()
:獲取錯(cuò)誤信息。Value()
:根據(jù)key
從Context
取值
為什么要使用Context
為什么要使用Context
?或者說設(shè)計(jì)Context
的目的是什么?
想像一下這樣的場(chǎng)景,當(dāng)用戶開啟瀏覽器訪問我們的Web
服務(wù)時(shí),我們可能會(huì)開啟多個(gè)Goroutine
來處理用戶的請(qǐng)求,這些Goroutine
需要讀取不同的資源,最終返回給用戶,但如果用戶在我們的Web服務(wù)還沒處理完成就關(guān)閉瀏覽器,斷開連接,而此時(shí)Web不知道用戶已經(jīng)關(guān)閉請(qǐng)求,仍然在處理并返回最終并不會(huì)被接收的數(shù)據(jù)。
Context
設(shè)計(jì)的目的就是可以從上游的Goroutine
發(fā)送信息給下游的Goroutine
,回到處理用戶請(qǐng)求的場(chǎng)景,當(dāng)處理請(qǐng)求的Goroutine
發(fā)現(xiàn)用戶斷開連接時(shí),通過Context
發(fā)送停止執(zhí)行的信息,而下游的Goroutine
得到停止信號(hào)時(shí)就返回,避免資源的浪費(fèi)。
概括起來,Context
的作用主要體現(xiàn)在兩個(gè)方面:
- 在
Goroutine
之間傳遞關(guān)閉信息,定時(shí)關(guān)閉,超時(shí)關(guān)閉,手動(dòng)關(guān)閉。 - 在
Goroutine
之間傳遞數(shù)據(jù)。
Context的使用
下面我們來講講Context
的基本使用。
創(chuàng)建Context
任何上下文都是從一個(gè)空白的Context
開始的,創(chuàng)建一個(gè)空白的Context
有兩種方式:
使用context.Background()
:
ctx?:=?context.Backgroud()
使用contenxt.TODO()
:
ctx?:=?context.TODO()
當(dāng)然大部分時(shí)候,我們不需要自己創(chuàng)建一個(gè)空白的Context
,比如在處理HTTP
請(qǐng)求時(shí):
http.HandleFunc("/hello",?func(w?http.ResponseWriter,?r?*http.Request)?{ ?ctx?:=?r.Context() })
上面的代碼中,可以從http.Request
中獲取Context
實(shí)例,而http.Request
的實(shí)際上也是調(diào)用context.Backgroud()
:
//net/http/request.go func?(r?*Request)?Context()?context.Context?{ ?if?r.ctx?!=?nil?{ ??return?r.ctx ?} ?return?context.Background() }
實(shí)例講解
為了講解Context
是如何在Goroutine
之間傳遞信號(hào)與數(shù)據(jù),我們通過下面的案例進(jìn)行說明:
func?ReadFile(ctx?context.Context,?fileName?string,?result?chan<-?[]byte)?{ ?file,?err?:=?os.Open(fileName) ?if?err?!=?nil?{ ??return ?} ?totalResult?:=?make([]byte,?0) ?for?{ ??select?{ ??case?<-ctx.Done(): ???result?<-?[]byte{} ???return ??default: ??} ??b?:=?make([]byte,?1024) ??_,?err?:=?file.Read(b) ??if?err?==?io.EOF?{ ???totalResult?=?append(totalResult,?b...) ???break ??} ??totalResult?=?append(totalResult,?b...) ?} ?result?<-?totalResult }
在上面的程序中,我們調(diào)用Context
的Done()
方法,該方法會(huì)返回一個(gè)Channel
,而使用select
語句則可以讓我們?cè)谔幚順I(yè)務(wù)的同時(shí),監(jiān)聽上游Goroutine
是否有傳遞取消執(zhí)行的信息。
利用Context傳遞停止信號(hào)
空白的Context
并不能發(fā)揮什么作用,要達(dá)到手動(dòng)取消執(zhí)行的目的,需要調(diào)用context
包下的WithCancel
函數(shù)進(jìn)行封裝,封裝返回一個(gè)新的context
以及一個(gè)取消的句柄cancel
函數(shù):
ctx?:=?context.Background() ctx,?cancel?:=?context.WithCancel(ctx)
下面是完整的使用方法:
package?main import?( ?"context" ?"fmt" ?"io" ?"os" ?"time" ) func?main()?{ ?ctx?:=?context.Background() ?ctx,?cancel?:=?context.WithCancel(ctx) ?result?:=?make(chan?[]byte) ?go?ReadFile(ctx,?"./test.tar",?result) ?time.Sleep(100?*?time.Millisecond) ?cancel() ?fmt.Println(<-result) }
在上面的程序中,我們創(chuàng)建一個(gè)Context
之后,傳給了ReadFile
函數(shù),并且在暫停100毫秒后調(diào)用cancel()
函數(shù),達(dá)到手動(dòng)取消另一個(gè)Goroutine
的目的。
給Context設(shè)置一個(gè)截止時(shí)間
除了手動(dòng)取消,也可以調(diào)用context
包下的WithDeadline()
函數(shù)給Context
加一個(gè)截止時(shí)間,這樣在某個(gè)時(shí)間點(diǎn),Context
會(huì)自動(dòng)發(fā)出取消信號(hào):
afterTime?:=?time.Now().Add(30?*?time.Millisecond) ctx,?cancel?:=?context.WithDeadline(context.Background(),?afterTime)
下面是完整的示例:
package?main import?( ?"context" ?"fmt" ?"io" ?"os" ?"time" ) func?main()?{ ?afterTime?:=?time.Now().Add(30?*?time.Millisecond) ?ctx,?cancel?:=?context.WithDeadline(context.Background(),?afterTime) ?defer?cancel() ?result?:=?make(chan?[]byte) ?go?ReadFile(ctx,?"./test.tar",?result) ?fmt.Println(<-result) }
給Context一個(gè)超時(shí)限制
調(diào)用context
包下的WithTimeout()
函數(shù)可以為Context
加一個(gè)超時(shí)限制,這對(duì)于我們編寫超時(shí)控制程序非常有幫助:
ctx,?cancel?:=?context.WithTimeout(context.Background(),?10*time.Millisecond)
下面是完整的調(diào)用程序:
package?main import?( ?"context" ?"fmt" ?"io" ?"os" ?"time" ) func?main()?{ ?ctx,?cancel?:=?context.WithTimeout(context.Background(),?10*time.Millisecond) ?defer?cancel() ?result?:=?make(chan?[]byte) ?go?ReadFile(ctx,?"./test.tar",?result) ?fmt.Println(<-result) }
使用Context傳遞數(shù)據(jù)
調(diào)用context
包下的WithValue()
函數(shù)可以生成一個(gè)攜帶數(shù)據(jù)的Context
,這個(gè)機(jī)制方便我們跟蹤一個(gè)處理流程中的Goroutine
:
?ctx,?cancel?:=?context.WithValue(context.Background(),?"testKey","testValue")
下游的Goroutine
就可以通過Context
的Value()
函數(shù)來獲取上游傳遞下來的值了。
func?MyGoroutine(ctx?context.Context){ ?ctx.Value("testKey") }
使用Context的幾點(diǎn)建議
Context
不要放在結(jié)構(gòu)體中,需要以參數(shù)方式傳遞。
Context
作為函數(shù)參數(shù)時(shí),一般放在第一位,作為函數(shù)的第一個(gè)參數(shù)。
使用 context.Background
函數(shù)生成根節(jié)點(diǎn)的Context
。
Context
要傳值必要的值,不要什么都傳。
Context
是多協(xié)程安全的,可以在多個(gè)協(xié)程中使用。
小結(jié)
好了,至此,你應(yīng)該對(duì)Context
有所了解了吧,總的來說,通過Context
可以做到:
- 手動(dòng)控制下游
Goroutine
取消執(zhí)行。 - 定時(shí)控制下游
Goroutine
取消執(zhí)行。 - 超時(shí)控制下游
Goroutine
取消執(zhí)行。 - 傳遞數(shù)據(jù)給下游的
Goroutine
。
到此這篇關(guān)于重學(xué)Go語言之如何使用Context的文章就介紹到這了,更多相關(guān)Go Context內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang WebView跨平臺(tái)的桌面應(yīng)用庫的使用
Golang WebView是一個(gè)強(qiáng)大的桌面應(yīng)用庫,本文介紹了Golang WebView的特點(diǎn)和使用方法,并列舉示例詳細(xì)的介紹了其在實(shí)際項(xiàng)目中的應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03golang中snappy的使用場(chǎng)合實(shí)例詳解
在java 和go語言 大字符傳達(dá)的時(shí)候, 采用snappy 壓縮 解壓縮是最好的方案。下面這篇文章主要給大家介紹了關(guān)于golang中snappy使用場(chǎng)合的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2017-12-12Go語言實(shí)現(xiàn)類似c++中的多態(tài)功能實(shí)例
Go本身不具有多態(tài)的特性,不能夠像Java、C++那樣編寫多態(tài)類、多態(tài)方法。但是,使用Go可以編寫具有多態(tài)功能的類綁定的方法。下面來一起看看吧2016-09-09golang 對(duì)私有函數(shù)進(jìn)行單元測(cè)試的實(shí)例
這篇文章主要介紹了golang 對(duì)私有函數(shù)進(jìn)行單元測(cè)試的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05解決Go中攔截HTTP流數(shù)據(jù)時(shí)字段丟失的問題
在開發(fā)高并發(fā)的Web應(yīng)用時(shí),尤其是在處理HTTP代理和流數(shù)據(jù)攔截的場(chǎng)景下,遇到數(shù)據(jù)丟失的問題并不罕見,最近,在一個(gè)項(xiàng)目中,我遇到了一個(gè)棘手的問題:在攔截并轉(zhuǎn)發(fā)HTTP流數(shù)據(jù)的過程中,某些數(shù)據(jù)字段因?yàn)樘幚磉^快而被丟失,所以本文給大家介紹如何解決這個(gè)問題2024-08-08詳解Golang中創(chuàng)建error的方式總結(jié)與應(yīng)用場(chǎng)景
Golang中創(chuàng)建error的方式包括errors.New、fmt.Errorf、自定義實(shí)現(xiàn)了error接口的類型等,本文主要為大家介紹了這些方式的具體應(yīng)用場(chǎng)景,需要的可以參考一下2023-07-07