上下文 Context 與結構體 Struct
原文地址:https://blog.golang.org/context-and-structs
原文作者:Jean de Klerk, Matt T. Proud
譯者:Kevin
介紹
在許多 Go API 中,尤其是現代的 API 中,函式和方法的第一個引數通常是context.Context
。上下文(Context)提供了一種方法,用於跨 API 邊界和程式之間傳輸截止時間、呼叫者取消和其他請求範圍的值。當一個庫與遠端伺服器(如資料庫、API 等)直接或間接互動時,經常會用到它。
在context 的文件中寫道。
上下文不應該儲存在結構型別裡面,而是傳遞給每個需要它的函式。
本文對這一建議進行了擴充套件,用具體例子解析為什麼傳遞上下文而不是將其儲存在其他型別中很重要。它還強調了一種罕見的情況,即在結構型別中儲存上下文可能是有意義的,以及如何安全地這樣做。
傾向於將上下文作為引數傳遞
為了深入理解不在結構中儲存上下文的建議,我們來考慮一下首選的上下文作為引數的方法。
type Worker struct { /* … */ }
type Work struct { /* … */ }
func New() *Worker {
return &Worker{}
}
func (w *Worker) Fetch(ctx context.Context) (*Work, error) {
_ = ctx // 每次呼叫中ctx用於取消操作,截止時間和後設資料。
}
func (w *Worker) Process(ctx context.Context, w *Work) error {
_ = ctx // A每次呼叫中ctx用於取消操作,截止時間和後設資料。
}
在這個例子中,(*Worker).Fetch
和(*Worker).Process
方法都直接接受上下文。通過這種通過引數傳遞的設計,使用者可以設定每次呼叫的截止時間、取消和後設資料。而且,很清楚傳遞給每個方法的context.Context
將如何被使用:沒有期望傳遞給一個方法的context.Context
將被任何其他方法使用。這是因為上下文的範圍被限定在了小範圍的必須操作內,這大大增加了這個包中上下文的實用性和清晰度。
將上下文儲存在結構中會導致混亂
讓我們再次使用上下文儲存在結構體中這種方式審視一下上面的Worker
例子。它的問題是,當你把上下文儲存在一個結構中時,你會向呼叫者隱藏它的生命週期,甚至可能的是把兩個不同的作用域以不可預料的方式互相干擾:
type Worker struct {
ctx context.Context
}
func New(ctx context.Context) *Worker {
return &Worker{ctx: ctx}
}
func (w *Worker) Fetch() (*Work, error) {
_ = w.ctx // 共享的w.ctx用於取消操作,截止時間和後設資料。
}
func (w *Worker) Process(w *Work) error {
_ = w.ctx // 共享的w.ctx用於取消操作,截止時間和後設資料。
}
(*Worker).Fetch
和(*Worker).Process
方法都使用儲存在Worker
中的上下文。這防止了Fetch
和Process
的呼叫者(它們本身可能有不同的上下文)在每次呼叫的基礎上指定截止日期、請求取消和附加後設資料。例如:使用者無法只為(*Worker).Fetch
提供截止日期,也無法只取消(*Worker).Process
的呼叫。呼叫者的生命期與共享上下文交織在一起,上下文的範圍是建立Worker
的生命週期。
與上下文作為引數的方法相比,該 API 也更容易讓使用者感到疑惑。使用者可能會問自己:
- 既然
New
需要一個context.Context
,那麼建構函式是否在做取消或截止時間控制的工作? -
New
傳遞進來的context.Context
是否適用於(*Worker).Fetch
和(*Worker).Process
?都不適用?有一個而沒有另一個?
API 需要大量的文件來明確告訴使用者context.Context
到底是用來做什麼的。使用者可能還需要閱讀程式碼,而不是能夠依靠 API 結構獲得資訊。
最後,如果設計一個生產級伺服器,其每個請求沒有上下文,從而不能充分重視取消操作,這可能是相當危險的。如果沒有能力設定每個呼叫的截止日期,你的程式可能會積壓資源並導致資源耗盡(如記憶體)!
規則的例外:儲存向後的相容性
當引入 context.Context的 Go 1.7 釋出時,大量的 API 必須以向後相容的方式新增上下文支援。例如,net/http
的Client
方法,如Get
和Do
,就是很好的上下文取消操作的應用。每一個用這些方法傳送的外部請求都會受益於context.Context
帶來的截止時間、取消和後設資料支援。
有兩種方法可以以向後相容的方式新增對context.Context
的支援:將上下文包在一個結構中,正如我們稍後將看到的那樣;複製函式,複製的函式接受context.Context
作為引數,並將Context
作為其函式名的字尾。複製的方法應該比在結構體中嵌入上下文的方式更可取,在保持模組的相容性中會進一步討論。然而,在某些情況下,這是不切實際的:例如,如果你的 API 暴露了大量的函式,那麼複製所有的函式可能是不可行的。
net/http
包選擇了上下文儲存在結構體方式,這提供了一個有用的案例研究。讓我們看看net/http
的Do
方法。在引入context.Context
之前,Do
的定義如下:
func (c *Client) Do(req *Request) (*Response, error)
在 Go 1.7 之後,如果不考慮破壞向後的相容性的問題,Do 可能看起來像下面這樣:
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)
但是,保留向後的相容性,遵守Go 1 的相容性承諾對於標準庫來說是至關重要的。所以,維護者選擇在http.Request
結構上新增一個context.Context
,以便在不破壞向後相容性的情況下支援context.Context
:
type Request struct {
ctx context.Context
// ...
}
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
// 為了本文演示需要做了簡化。
return &Request{
ctx: ctx,
// ...
}
}
func (c *Client) Do(req *Request) (*Response, error)
當改造你的 API 以支援上下文時,像上面那樣在一個結構中新增一個context.Context
可能是有意義的。但是,你需要首先考慮複製你的函式,這樣可以在不犧牲實用性和理解性的前提下,向後相容地改造context.Context
。例如:
func (c *Client) Call() error {
return c.CallContext(context.Background())
}
func (c *Client) CallContext(ctx context.Context) error {
// ...
}
總結
上下文使得重要的跨庫和跨 API 資訊很容易在呼叫棧中傳播。但是,為了保持可理解性、易除錯性和有效性,必須統一清晰地使用它。
當作為方法中的第一個引數而不是儲存在結構型別中時,使用者可以充分利用它的可擴充套件性,以便通過呼叫棧建立一個強大的取消、截止日期和後設資料資訊樹。而且,最重要的是,當它作為一個引數傳遞進來時,它的範圍被清晰的理解,從而導致堆疊上下的理解更加清晰和除錯更加容易。
當設計一個帶有上下文的 API 時,請記住這樣的建議:將context.Context
作為一個引數傳遞進來,不要將它儲存在結構體中。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- struct結構體專案1Struct結構體
- odoo context上下文用法總結OdooContext
- go 結構體 (struct) 和方法 (method)Go結構體Struct
- Golang 學習——結構體 struct (一)Golang結構體Struct
- Golang 學習——結構體 struct (二)Golang結構體Struct
- 瞭解下C# 結構體(Struct)C#結構體Struct
- go 上下文:context.ContextGoContext
- golang 學習之路之 struct 結構體GolangStruct結構體
- struct 結構體 -Go 學習記錄Struct結構體Go
- struct 和 interface:結構體與介面都實現了哪些功能?Struct結構體
- C++ struct結構體記憶體對齊C++Struct結構體記憶體
- [Java] 深入理解:Spring Context :Spring 上下文(ApplicationContext)的層次結構JavaSpringContextAPP
- C# 中的只讀結構體(readonly struct)C#結構體Struct
- struct結構體大小的計算(記憶體對齊)Struct結構體記憶體
- React的上下文-ContextReactContext
- Solidity語言學習筆記————15、結構體StructSolid筆記結構體Struct
- C語言中結構體struct的對齊問題C語言結構體Struct
- Go中struct巢狀與物件導向以及上下文GoStruct巢狀物件
- Golang context (上下文)是什麼GolangContext
- CSS 層疊上下文(Stacking Context)CSSContext
- 結構體與共用體結構體
- mysql表結構自動生成golang structMySqlGolangStruct
- 類與結構體結構體
- iris 路由註冊和上下文context路由Context
- 胡塞爾現象=上下文ContextContext
- Python - Context Manager 上下文管理器PythonContext
- [swift 進階]讀書筆記-第五章:結構體和類 C5P3_結構體(struct)Swift筆記結構體Struct
- Golang中struct結構標籤(Tag)的使用GolangStruct
- rust學習六、簡單的struct結構RustStruct
- Go語言結構體(struct)物件導向程式設計基礎篇Go結構體Struct物件程式設計
- Swift 類與結構體Swift結構體
- 獲取管理中心上下文ContextContext
- 【Golang】Go 通過結構(struct) 實現介面(interface)GolangStruct
- MySQL整體架構與記憶體結構MySql架構記憶體
- Structs And Interfaces「結構體與介面」Struct結構體
- 一起學context(一)——上下文值傳遞Context
- with open() as 的用法 和 with上下文管理器(Context manager)Context
- 什麼是Python中Context上下文管理器PythonContext