引子
在 Java
語言當中,特別是在 Spring
語境下,通常我們會遇到處理上下文的需求。一般場景中,我們可以利用 java.lang.ThreadLocal
來實現,基於執行緒維度對變數進行管理。ThreadLocal
執行緒記憶體儲和訪問變數的機制,非常適合在單個請求的生命週期內傳遞上下文資訊。
下面是個簡單的請求上下文的例子:
public class RequestContext {
private static final ThreadLocal<RequestContext> threadLocal = ThreadLocal.withInitial(RequestContext::new);
private String userId;
private String requestId;
public static RequestContext getCurrent() {
return threadLocal.get();
}
public static void setCurrent(RequestContext context) {
threadLocal.set(context);
}
public static void clear() {
threadLocal.remove();
}
// Getters and setters
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
}
使用的話,可以在攔截器中實現初始化賦值或者清楚資料。PS:請注意執行緒安全的問題和遵守 java.lang.ThreadLocal
最佳實現,切莫自創用法。
在 Go
語言中,基於 goroutine
進行上下文管理的就是本文的主角 context
包。
簡介
Go
語言的 context
包是在 Go 1.7
版本引入的,用於在不同的 goroutine
之間傳遞請求範圍內的值、取消訊號和截止日期。它在處理併發操作時非常有用,可以透過 context
物件來控制和管理 goroutine 的生命週期。
context
包的核心型別是 Context
介面,它包含四個方法:Deadline
、Done
、Err
和 Value
。Deadline
方法返回操作的截止時間,Done
方法返回一個通道,當操作應該取消時,該通道會關閉,Err
方法返回取消的錯誤原因,Value
方法允許儲存和檢索鍵值對。
在實際使用中,context
包常用於網路請求、資料庫操作和其他需要取消和超時控制的操作。透過在函式間傳遞 Context
物件,可以實現更靈活和可控的併發操作,避免 goroutine 洩漏和資源浪費。
建立方法
Background
在 Go 語言的 context 包中,context.Background() 用於返回一個空的上下文,它通常作為根上下文使用。這個根上下文在整個程式生命週期記憶體在,永遠不會被取消或超時。context.Background() 常用於初始化傳遞給其他上下文的頂層上下文,例如在啟動伺服器或處理請求時使用。
ctx := context.Background()
TODO
在 Go 語言的 context 包中,context.TODO() 返回一個空的上下文,它與 context.Background() 相似,但其主要用途是作為佔位符。通常在程式碼尚未確定具體上下文需求時使用 context.TODO(),以便稍後替換為適當的上下文。
ctx := context.TODO()
WithCancel
在 Go 語言的 context 包中,context.WithCancel 返回一個可取消的上下文及其取消函式。這個函式用於建立一個新的上下文,當呼叫返回的取消函式時,該上下文及其所有子上下文都會被取消。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
WithDeadline
在 Go 語言的 context 包中,context.WithDeadline 返回一個上下文,該上下文會在指定的時間點自動取消。這種方式對於需要在特定時間點之前完成操作的場景非常有用。
// 建立一個根上下文
rootCtx := context.Background()
// 設定一個未來的時間點
deadline := time.Now().Add(3 * time.Second)
// 基於根上下文建立一個具有截止時間的子上下文
_, cancel := context.WithDeadline(rootCtx, deadline)
cancel() // 確保在操作完成後取消上下文
WithTimeout
在 Go 語言的 context 包中,context.WithTimeout 是一個非常常用的函式,它建立一個帶有超時的上下文。與 context.WithDeadline 類似,context.WithTimeout 會在指定的時間段後自動取消上下文。這對於需要在限定時間內完成的任務非常有用。
// 建立一個根上下文
rootCtx := context.Background()
// 基於根上下文建立一個具有 2 秒超時的子上下文
_, cancel := context.WithTimeout(rootCtx, 2*time.Second)
defer cancel() // 確保在操作完成後取消上下文
WithValue
在 Go 語言的 context 包中,context.WithValue 用於建立一個新的上下文,該上下文攜帶了特定的鍵值對。這個功能允許在上下文中傳遞請求範圍內的特定資料,如使用者認證資訊、配置選項等。與 context.WithCancel 和 context.WithTimeout 不同,context.WithValue 主要用於儲存和傳遞資料,而不是控制上下文的生命週期。
// 建立根上下文
rootCtx := context.Background()
// 使用 WithValue 建立一個新的上下文,並傳遞使用者資訊
ctx := context.WithValue(rootCtx, "user", "FunTester")
// 從上下文中檢索使用者資訊
user, ok := ctx.Value("user").(string)
if ok {
fmt.Println("使用者:", user)
}
常用方法
Deadline
在 Go 語言中,context 包提供了 Deadline 方法,用於獲取上下文的截止時間。這在使用 context.WithDeadline 或 context.WithTimeout 建立的上下文時特別有用。
// 建立一個根上下文
rootCtx := context.Background()
// 設定一個 3 秒後的截止時間
deadline := time.Now().Add(3 * time.Second)
ctx, cancel := context.WithDeadline(rootCtx, deadline)
defer cancel() // 確保在操作完成後取消上下文
// 檢索截止時間
d, ok := ctx.Deadline()
if ok {
fmt.Println("截止時間:", d.Format("2006-01-02 15:04:05"))
} else {
fmt.Println("沒有設定截止時間")
}
Done
在 Go 語言的 context 包中,Done 方法是用於獲取上下文的取消訊號通道。當上下文被取消時,Done 方法返回的通道會接收到一個訊號。這對於處理超時、取消操作和清理工作非常重要。
package main
import (
"context"
"fmt" "time")
func main() {
// 基於根上下文建立一個具有 2 秒超時的上下文
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 確保在操作完成後取消上下文
// 啟動一個 goroutine 執行一些工作
go func(ctx context.Context) {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("任務進行中")
case <-ctx.Done():
fmt.Println("任務被取消:", ctx.Err())
return
}
}
}(ctx)
// 等待 3 秒鐘,以觀察超時是否生效
time.Sleep(3 * time.Second)
}
Err
在 Go 語言的 context 包中,Err 方法用於獲取上下文取消的錯誤資訊。它返回一個錯誤值,指示上下文的取消原因。這對於確定任務是否因超時、手動取消或其他原因終止非常有用。
package main
import (
"context"
"fmt" "time")
func main() {
// 建立一個具有 2 秒超時的子上下文
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 確保在操作完成後取消上下文
// 啟動一個 goroutine 執行一些工作
go func(ctx context.Context) {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("任務進行中")
case <-ctx.Done():
// 使用 Err 方法獲取取消原因
err := ctx.Err()
if err != nil {
fmt.Println("任務被取消:", err)
}
return
}
}
}(ctx)
// 等待 3 秒鐘,以觀察超時是否生效
time.Sleep(3 * time.Second)
fmt.Println("程式結束")
}
Value
在 Go 語言的 context 包中,Value 方法用於從上下文中檢索儲存的資料。Value 方法允許你在上下文中儲存和檢索特定的鍵值對,這對於在上下文中傳遞請求範圍的資料非常有用。
這個例子在之前 WithValue
中已經用到了,這裡不再重複。
併發中的應用
goroutine 的取消
在使用 Go 語言進行併發程式設計時,context
包提供了一種優雅的方式來控制 goroutine 的生命週期。透過context.WithCancel
函式,我們可以建立一個新的 context 例項,該例項可以被取消。
- 當主 goroutine 決定不再需要某個操作繼續執行時,可以呼叫 context 的
cancel
函式。 - 所有使用該 context 的 goroutine 都可以透過監聽 context 的
Done()
通道來感知到取消訊號,並做出相應的清理工作,然後退出。
例如,以下程式碼展示瞭如何使用context
來控制兩個 goroutine 的取消:
package main
import (
"context"
"fmt" "time")
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
// 處理取消邏輯
fmt.Println("goroutine exit, cancel done")
return
default:
// 執行常規任務
}
}
}()
// 主goroutine可以在任何時候呼叫cancel來停止上述goroutine
time.Sleep(1 * time.Second)
cancel()
}
4.2 超時控制
context
包同樣支援超時控制,這在很多場景下非常有用,比如 API 呼叫、資料庫訪問等操作。透過context.WithTimeout
函式,我們可以為 context
設定一個超時時間。
- 當設定的超時時間到達後,
context
會自動被取消,所有監聽Done()
通道的 goroutine 都會收到通知。 - 這種方式可以防止程式因為某個長時間執行的操作而卡住。
以下示例演示瞭如何使用context
來進行超時控制:
package main
import (
"context"
"fmt" "time")
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
// 處理取消邏輯
fmt.Println("goroutine exit, cancel done")
return
default:
// 執行常規任務
}
}
}()
// 主goroutine可以在任何時候呼叫cancel來停止上述goroutine
time.Sleep(1 * time.Second)
cancel()
}
在 Go 語言中,context
包的設計初衷是為了簡化併發程式設計中的一些常見問題,比如在多個goroutine
之間傳遞請求範圍的資料、處理超時和取消訊號等。它透過提供一個可以被傳遞給多個函式的請求上下文,使得程式碼更加清晰和易於管理。
在網路程式設計中的應用
在 Go 語言中,context
包是處理併發請求時不可或缺的工具,尤其是在網路程式設計中。它允許開發者傳遞請求範圍的值、取消訊號和截止時間,從而實現對 HTTP 請求的精細控制。
-
請求取消:在處理 HTTP 請求時,客戶端可能會取消請求。透過
context
,我們可以檢測到這種取消訊號,並及時終止正在執行的請求處理邏輯,避免資源浪費。 -
超時控制:網路請求往往需要設定超時,以避免伺服器資源被長時間佔用。利用
context
的超時功能,我們可以為每個請求設定合理的超時時間,提高服務的響應性和健壯性。 -
請求資料傳遞:在處理複雜的 HTTP 請求時,我們可能需要在不同的處理階段傳遞額外的資料。
context
提供了一種機制,允許我們將這些資料儲存在請求的上下文中,方便跨階段訪問。
以下是context
在 HTTP 請求中使用的具體示例:
package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func main() {
client := http.Client{
Timeout: time.Second * 10, // 設定客戶端超時時間
}
// 建立一個帶有超時的context
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
// 建立一個HTTP請求
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://example.com", nil)
// 傳送請求
resp, _ := client.Do(req)
defer resp.Body.Close()
// 讀取響應體
body, _ := io.ReadAll(resp.Body)
fmt.Println("Response body:", string(body))
}
在這個示例中,我們首先建立了一個http.Client
例項,並設定了超時時間。然後,我們使用 context.WithTimeout
建立了一個帶有超時的 context
,這個 context
被用於建立和傳送 HTTP 請求。如果請求在超時時間內沒有完成,context
會觸發取消訊號,導致請求被中斷。透過這種方式,context
包在網路程式設計中的應用可以顯著提高 HTTP 請求處理的靈活性和效率。
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI 自動化
- 測試理論雞湯
- 社群風采&影片合集