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