当前位置:网站首页>一種獲取context中keys和values的高效方法 | golang

一種獲取context中keys和values的高效方法 | golang

2021-01-23 23:22:49 itread01

我們知道,在 golang 中的 context 是一個非常重要的包,儲存了程式碼活動的上下文。我們經常使用 WithValue() 這個方法,來往 context 中 傳遞一些 key value 資料。如果我們想拿到 context 中所有的 key and value 或者在不知道 key 的情況想獲得value 要怎麼做呢?這裡提供一個比較hacker的實現給大家參考。## 調研首先,看看WithValue到底發生了什麼:```package contextfunc WithValue(parent Context, key, val interface{}) Context { return &valueCtx{parent, key, val}}// A valueCtx carries a key-value pair. It implements Value for that key and// delegates all other calls to the embedded Context.type valueCtx struct { Context key, val interface{}}```WithValue 通過把 key value 塞到了 valueCtx 的 struct 中,將資料儲存下來。通過研究 context 包我們發現,不同的 功能的context有不同的實現```package context// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to// implement Done and Err. It implements cancel by stopping its timer then// delegating to cancelCtx.cancel.type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time}// A cancelCtx can be canceled. When canceled, it also cancels any children// that implement canceler.type cancelCtx struct { Context mu sync.Mutex // protects following fields done chan struct{} // created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call}```但無一例外,全部是私有 struct ,同時是通過連結串列的形式將各個 context 串在一起的。這種情況對與我們想要做的事情是比較不好的,我們無法將 context 介面轉換成實現來獲取其內部儲存的 key and value,而且由於有多種實現,我們無法得知下一個 context 是不是 valueCtx,需不需要跳過。## 思路既然這樣,我們就只能用一些比較 hacker 的方法了:1. 定義一個自己的 valueCtx 內部資料結構與 context 包中一致2. 通過 unsafe.Pointer() 繞過型別檢測,強制將 context.valueCtx 轉換成我們的 valueCtx3. 獲取內部的值儲存在 map 中## 實踐首先自定義一個我們自己的 valueCtx ,直接照搬 context 的實現就行:```package maintype valueCtx struct { context.Context key, val interface{}}```然後強轉並列印:```package mainfunc main() { ctx := context.Background() ctx = context.WithValue(ctx, "key1", "value1") valCtx := (*valueCtx)(unsafe.Pointer(&ctx)) fmt.Printf("key: %v, value: %v", valCtx.key, valCtx.val) // panic: runtime error: invalid memory address or nil pointer dereference}```事情並沒有按我們預想的發生,這在程式設計的過程中再常見不過了,一定是有什麼不對。在這種時候我們就應該去翻閱資料,去搞懂我們還模糊的部分,就會找到原因。不過,既然是文章,我就直接寫我的結論了。這個要從介面型別的實現說起,golang 的介面的概念和實現是比較具體和複雜的,如果想進一步深入,請參閱這篇Stefno的[文章](https://www.cnblogs.com/qcrao-2018/p/10766091.html),其非常深入和詳細的講解了 golang 中介面的方方面面。好了,回到我們的問題,現在對於 golang 的介面,我們只需要知道:在 golang 的接口裡儲存兩個東西,其一是介面所持有的動態型別,其二是動態型別的值。結構如下:```type iface struct { itab, data uintptr}```也就是說,當我們在轉換介面的時候,其實是再把上面的結構強轉為 valueCtx ,但其實我們期望的資料應該是儲存在介面的動態型別值裡,因此我們應該強轉的是 iface.data。要做到這點,我們就需要將 context 先轉換成 iface,再獲取其中的 data:```package maintype iface struct { itab, data uintptr}type valueCtx struct { context.Context key, val interface{}}func main() { ctx := context.Background() ctx = context.WithValue(ctx, "key1", "value1") ictx := (*iface)(unsafe.Pointer(&ctx)) valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) fmt.Printf("key: %v, value: %v", valCtx.key, valCtx.val) // output: // key: key1, value: value1}```這回,我們終於獲得其中的資料了。## 完善接下來,我們需要把 context 中的所有 key value 獲取出來:```package mainfunc GetKeyValues(ctx context.Context) map[interface{}]interface{} { m := make(map[interface{}]interface{}) getKeyValue(ctx, m) return m}func getKeyValue(ctx context.Context, m map[interface{}]interface{}) { ictx := *(*iface)(unsafe.Pointer(&ctx)) if ictx.data == 0 { return } valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) if valCtx != nil && valCtx.key != nil && valCtx.val != nil { m[valCtx.key] = valCtx.val } getKeyValue(valCtx.Context, m)}```通過遞迴呼叫,我們可以很輕鬆的遍歷所有 context,同時 golang 中的 nil 在底層其實就是一個int的0,這就是為什麼我們把 nil 叫做 零值。所以,遞迴的退出條件也很簡單,就是當 iface 中的 data 為0時,說明我們已經查詢到 context 中的最後一個了。好了,以上就是所有的內容了,如果你想獲取文章的具體實現和 demo 示例,可以到我的 [github](https://github.com/liujh2010/code_demo_of_cnblogs/blob/main/ctx-printer/main.go) 上找到,謝謝你的閱讀。

版权声明
本文为[itread01]所创,转载请带上原文链接,感谢
https://www.itread01.com/content/1611413674.html

随机推荐