Go語言基礎(chǔ)學(xué)習(xí)之Context的使用詳解
前言
在 Go 語言中,Context 是一個(gè)非常重要的概念,它用于在不同的 goroutine 之間傳遞請(qǐng)求域的相關(guān)數(shù)據(jù),并且可以用來控制 goroutine 的生命周期和取消操作。本文將深入探討 Go 語言中 Context 特性 和 Context 的高級(jí)使用方法。
基本用法
在 Go 語言中,Context 被定義為一個(gè)接口類型,它包含了三個(gè)方法:
# go version 1.18.10 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any }
- Deadline() 方法用于獲取 Context 的截止時(shí)間,
- Done() 方法用于返回一個(gè)只讀的 channel,用于通知當(dāng)前 Context 是否已經(jīng)被取消,
- Err() 方法用于獲取 Context 取消的原因,
- Value() 方法用于獲取 Context 中保存的鍵值對(duì)數(shù)據(jù)。
我們?nèi)粘>帉懘a時(shí),Context 對(duì)象會(huì)被被約定作為函數(shù)的第一個(gè)參數(shù)傳遞,eg:
func users(ctx context.Context, request *Request) { // ... code }
在函數(shù)中,可以通過 ctx 參數(shù)來獲取相關(guān)的 Context 數(shù)據(jù),舉個(gè)超時(shí)的 eg:
deadline, ok := ctx.Deadline() if ok && deadline.Before(time.Now()) { // 超時(shí) return }
Context控制goroutine的生命周期
在 Go 語言中,goroutine 是一種非常常見的并發(fā)編程模型,而 Context 可以被用來控制 goroutine 的生命周期,從而避免出現(xiàn) goroutine 泄漏或者不必要的等待操作。
eg,看一下下方代碼:
func users(ctx context.Context, req *Request) { // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func() { // 處理請(qǐng)求... }() }
上面的代碼中,我們啟動(dòng)了一個(gè) goroutine 來處理請(qǐng)求,但是沒有任何方式來控制這個(gè) goroutine 的生命周期,如果這個(gè)請(qǐng)求被取消了,那么這個(gè) goroutine 就會(huì)一直存在,直到它完成為止。為了避免這種情況的發(fā)生,我們可以使用 Context 來控制 goroutine 的生命周期,eg:
func users(ctx context.Context, req *Request) { // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func(ctx context.Context) { // 處理請(qǐng)求... }(ctx) }
在上面的代碼中,我們將 Context 對(duì)象作為參數(shù)傳遞給了 goroutine 函數(shù),這樣在請(qǐng)求被取消時(shí),goroutine 就可以及時(shí)退出。
使用 WithValue() 傳遞數(shù)據(jù)
除了用于控制 goroutine 的生命周期,Context 還可以被用來在不同的 goroutine 之間傳遞請(qǐng)求域的相關(guān)數(shù)據(jù)。為了實(shí)現(xiàn)這個(gè)目的,我們可以使用 Context 的 WithValue() 方法,eg:
type key int const ( userKey key = iota ) func users(ctx context.Context, req *Request) { // 從請(qǐng)求中獲取用戶信息 user := req.GetUser // 將用戶信息保存到 Context 中 ctx = context.WithValue(ctx, userKey, user) // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func(ctx context.Context) { // 從 Context 中獲取用戶信息 user := ctx.Value(userKey).(*User) // 處理請(qǐng)求... }(ctx) }
在上面的代碼中,我們定義了一個(gè) key
類型的常量 userKey
,然后在 users()
函數(shù)中將用戶信息保存到了 Context 中,并將 Context 對(duì)象傳遞給了 goroutine 函數(shù)。
在 goroutine 函數(shù)中,我們使用 ctx.Value()
方法來獲取 Context 中保存的用戶信息。
注:
Context 中保存的鍵值對(duì)數(shù)據(jù)應(yīng)該是線程安全的,因?yàn)樗鼈兛赡軙?huì)在多個(gè) goroutine 中同時(shí)訪問。
使用 WithCancel() 取消操作
除了控制 goroutine 的生命周期和傳遞數(shù)據(jù)之外,Context 還可以被用來執(zhí)行取消操作。為了實(shí)現(xiàn)這個(gè)目的,我們可以使用 Context 的 WithCancel()
方法,eg:
func users(ctx context.Context, req *Request) { // 創(chuàng)建一個(gè)可以取消的 Context 對(duì)象 ctx, cancel := context.WithCancel(ctx) // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func(ctx context.Context) { // 等待請(qǐng)求完成或者被取消 select { case <-time.After(time.Second): // 請(qǐng)求完成 fmt.Println("Request finish") case <-ctx.Done(): // 請(qǐng)求被取消 fmt.Println("Request canceled") } }(ctx) // 等待一段時(shí)間后取消請(qǐng)求 time.Sleep(time.Millisecond * 800) cancel() }
在上面的代碼中,我們使用 WithCancel() 方法創(chuàng)建了一個(gè)可以取消的 Context 對(duì)象,并將取消操作封裝在了一個(gè) cancel() 函數(shù)中。然后我們啟動(dòng)了一個(gè) goroutine 函數(shù),使用 select 語句等待請(qǐng)求完成或者被取消,最后在主函數(shù)中等待一段時(shí)間后調(diào)用 cancel() 函數(shù)來取消請(qǐng)求。
使用 WithDeadline() 設(shè)置截止時(shí)間
除了使用 WithCancel() 方法進(jìn)行取消操作之外,Context 還可以被用來設(shè)置截止時(shí)間,以便在超時(shí)的情況下取消請(qǐng)求。為了實(shí)現(xiàn)這個(gè)目的,我們可以使用 Context 的 WithDeadline() 方法,eg:
func users(ctx context.Context, req *Request) { // 設(shè)置請(qǐng)求的截止時(shí)間為當(dāng)前時(shí)間加上 1 秒鐘 ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second)) // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func(ctx context.Context) { // 等待請(qǐng)求完成或者超時(shí) select { case <-time.After(time.Millisecond * 500): // 請(qǐng)求完成 fmt.Println("Request finish") case <-ctx.Done(): // 請(qǐng)求超時(shí)或者被取消 fmt.Println("Request canceled or timed out") } }(ctx) // 等待一段時(shí)間后取消請(qǐng)求 time.Sleep(time.Millisecond * 1500) cancel() }
在上面的代碼中,我們使用 WithDeadline() 方法設(shè)置了一個(gè)截止時(shí)間為當(dāng)前時(shí)間加上 1 秒鐘的 Context 對(duì)象,并將超時(shí)操作封裝在了一個(gè) cancel() 函數(shù)中。然后我們啟動(dòng)了一個(gè) goroutine 函數(shù),使用 select 語句等待請(qǐng)求完成或者超時(shí),最后在主函數(shù)中等待一段時(shí)間后調(diào)用 cancel() 函數(shù)來取消請(qǐng)求。
注:
在使用 WithDeadline() 方法設(shè)置截止時(shí)間的時(shí)候,如果截止時(shí)間已經(jīng)過期,則 Context 對(duì)象將被立即取消。
使用 WithTimeout() 設(shè)置超時(shí)時(shí)間
除了使用 WithDeadline() 方法進(jìn)行截止時(shí)間設(shè)置之外,Context 還可以被用來設(shè)置超時(shí)時(shí)間。為了實(shí)現(xiàn)這個(gè)目的,我們可以使用 Context 的 WithTimeout() 方法,eg:
func users(ctx context.Context, req *Request) { // 設(shè)置請(qǐng)求的超時(shí)時(shí)間為 1 秒鐘 ctx, cancel := context.WithTimeout(ctx, time.Second) // 啟動(dòng)一個(gè) goroutine 來處理請(qǐng)求 go func(ctx context.Context) { // 等待請(qǐng)求完成或者超時(shí) select { case <-time.After(time.Millisecond * 500): // 請(qǐng)求完成 fmt.Println("Request completed") case <-ctx.Done(): // 請(qǐng)求超時(shí)或者被取消 fmt.Println("Request canceled or timed out") } }(ctx) // 等待一段時(shí)間后取消請(qǐng)求 time.Sleep(time.Millisecond * 1500) cancel() }
在上面的代碼中,我們使用 WithTimeout() 方法設(shè)置了一個(gè)超時(shí)時(shí)間為 1 秒鐘的 Context 對(duì)象,并將超時(shí)操作封裝在了一個(gè) cancel() 函數(shù)中。然后我們啟動(dòng)了一個(gè) goroutine 函數(shù),使用 select 語句等待請(qǐng)求完成或者超時(shí),最后在主函數(shù)中等待一段時(shí)間后調(diào)用 cancel() 函數(shù)來取消請(qǐng)求。
注:
需要注意的是,在使用 WithTimeout() 方法設(shè)置超時(shí)時(shí)間的時(shí)候,如果超時(shí)時(shí)間已經(jīng)過期,則 Context 對(duì)象將被立即取消。
Context的傳遞
在一個(gè)應(yīng)用程序中,不同的 goroutine 可能需要共享同一個(gè) Context 對(duì)象。為了實(shí)現(xiàn)這個(gè)目的,Context 對(duì)象可以通過函數(shù)調(diào)用或者網(wǎng)絡(luò)傳輸?shù)确绞竭M(jìn)行傳遞。
例如,我們可以在一個(gè)處理 HTTP 請(qǐng)求的函數(shù)中創(chuàng)建一個(gè) Context 對(duì)象,并將它作為參數(shù)傳遞給一個(gè)數(shù)據(jù)庫(kù)查詢函數(shù),以便在查詢過程中使用這個(gè) Context 對(duì)象進(jìn)行取消操作。代碼 eg:
func users(ctx context.Context, req *Request) { // 在處理 HTTP 請(qǐng)求的函數(shù)中創(chuàng)建 Context 對(duì)象 ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() // 調(diào)用數(shù)據(jù)庫(kù)查詢函數(shù)并傳遞 Context 對(duì)象 result, err := findUserByName(ctx, req) if err != nil { // 處理查詢錯(cuò)誤... } // 處理查詢結(jié)果... } func findUserByName(ctx context.Context, req *Request) (*Result, error) { // 在數(shù)據(jù)庫(kù)查詢函數(shù)中使用傳遞的 Context 對(duì)象 rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE name = ?", req.Name) if err != nil { // 處理查詢錯(cuò)誤... } defer rows.Close() // 處理查詢結(jié)果... }
在上面的代碼中,我們?cè)谔幚?HTTP 請(qǐng)求的函數(shù)中創(chuàng)建了一個(gè) Context 對(duì)象,并將它作為參數(shù)傳遞給了一個(gè)數(shù)據(jù)庫(kù)查詢函數(shù) findUserByName()
。在 findUserByName()
函數(shù)中,我們使用傳遞的 Context 對(duì)象來調(diào)用 db.QueryContext()
方法進(jìn)行查詢操作。由于傳遞的 Context 對(duì)象可能會(huì)在查詢過程中被取消,因此我們需要在查詢完成后檢查查詢操作的錯(cuò)誤,以便進(jìn)行相應(yīng)的處理。
注:
在進(jìn)行 Context 的傳遞時(shí),我們需要保證傳遞的 Context 對(duì)象是原始 Context 對(duì)象的子 Context,以便在需要取消操作時(shí)能夠同時(shí)取消所有相關(guān)的 goroutine。如果傳遞的 Context 對(duì)象不是原始 Context 對(duì)象的子 Context,則取消操作只會(huì)影響到當(dāng)前 goroutine,而無法取消其他相關(guān)的 goroutine。
總結(jié)
在 Go 語言中,Context 是一個(gè)非常重要的特性,包括其基本用法和一些高級(jí)用法。Context 可以用于管理 goroutine 的生命周期和取消操作,避免出現(xiàn)資源泄漏和死鎖等問題,同時(shí)也可以提高應(yīng)用程序的性能和可維護(hù)性。
在使用 Context 的時(shí)候,需要注意以下幾點(diǎn):
- 在創(chuàng)建 goroutine 時(shí),需要將原始 Context 對(duì)象作為參數(shù)傳遞給它。
- 在 goroutine 中,需要使用傳遞的 Context 對(duì)象來進(jìn)行取消操作,以便能夠及時(shí)釋放相關(guān)的資源。
- Context 的傳遞時(shí),需要保證傳遞的 Context 對(duì)象是原始 Context 對(duì)象的子 Context,以便在需要取消操作時(shí)能夠同時(shí)取消所有相關(guān)的 goroutine。
- 在使用 WithCancel 和 WithTimeout 方法創(chuàng)建 Context 對(duì)象時(shí),需要及時(shí)調(diào)用 cancel 函數(shù),以便能夠及時(shí)釋放資源。
- 在一些場(chǎng)景下,可以使用 WithValue 方法將數(shù)據(jù)存儲(chǔ)到 Context 中,以便在不同的 goroutine 之間共享數(shù)據(jù)。
到此這篇關(guān)于Go語言基礎(chǔ)學(xué)習(xí)之Context的使用詳解的文章就介紹到這了,更多相關(guān)Go語言Context內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼功能
這篇文章主要介紹了go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼,使用json配置文件及配置文件解析,編寫路由controller層,本文通過代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Golang使用Gin框架實(shí)現(xiàn)HTTP響應(yīng)格式統(tǒng)一處理
在gin框架中,我們可以定義一個(gè)中間件來處理統(tǒng)一的HTTP響應(yīng)格式,本文主要為大家介紹了具體是怎么定義實(shí)現(xiàn)這樣的中間件的,感興趣的小伙伴可以了解一下2023-07-07GO語言運(yùn)行環(huán)境下載、安裝、配置圖文教程
這篇文章主要介紹了GO語言運(yùn)行環(huán)境下載、安裝、配置圖文教程,需要的朋友可以參考下2017-02-02Go語言中結(jié)構(gòu)體方法副本傳參與指針傳參的區(qū)別介紹
這篇文章主要給大家介紹了關(guān)于Go語言中結(jié)構(gòu)體方法副本傳參與指針傳參的區(qū)別的相關(guān)資料,文中先對(duì)GO語言結(jié)構(gòu)體方法跟結(jié)構(gòu)體指針方法的區(qū)別進(jìn)行了一些簡(jiǎn)單的介紹,來幫助大家理解學(xué)習(xí),需要的朋友可以參考下。2017-12-12GoLang日志監(jiān)控系統(tǒng)實(shí)現(xiàn)
這篇文章主要介紹了GoLang日志監(jiān)控系統(tǒng)的實(shí)現(xiàn)流程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12