当前位置:网站首页>An efficient method to get keys and values in context

An efficient method to get keys and values in context

2021-01-23 20:16:24 r1chard

We know , stay golang Medium context It's a very important bag , Save the context of the code activity . We use it a lot WithValue() This method , Dealings context in Pass on some key value data .
If we want to get context All of the key and value Or I don't know key I'd like to know more about it value How to do it ? Here's a comparison hacker For your reference .

research

First , have a look WithValue What happened :

package context

func 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 Through the key value Here it is valueCtx Of struct in , Save the data .

Through research context We found that , Different Functional context There are different implementations

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
}

But without exception , It's all private struct , At the same time is through the form of linked list will each context String together .

This situation is not good for what we want to do , We can't put context The interface is converted into an implementation to get the internal saved key and value, And because there are multiple implementations , We don't know what's next context Is it right? valueCtx, Do you need to skip .

Ideas

In this case , We can only use some comparisons hacker The method of the :

  1. Define one's own valueCtx Internal data structure and context It's the same in the package
  2. adopt unsafe.Pointer() Bypass type detection , Force will context.valueCtx Into our valueCtx
  3. Get the internal value and save it in the map in

practice

First, customize one of our own valueCtx , Copy directly context It's just the realization of :

package main

type valueCtx struct {
	context.Context
	key, val interface{}
}

And then force it to print :

package main

func 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
}

It didn't happen as we expected , This is very common in programming , There must be something wrong . At this time, we should go through the materials , To understand what we're still blurring , You'll find out why . however , Since it's an article , I'll just write my conclusion .

This starts with the implementation of interface types ,golang The concept and implementation of the interface is more specific and complex , If you want to go further , Please refer to this article Stefno Of article , It's very thorough and detailed golang All aspects of the interface in . Okay , Back to our question , For now golang The interface of , We just need to know : stay golang Two things are stored in the interface of , One is the dynamic type held by the interface , The second is the value of dynamic type .

The structure is as follows :

type iface struct {
	itab, data uintptr
}

in other words , When we're at the interface , In fact, it is to change the above structure into valueCtx , But in fact, we expect the data to be stored in the dynamic type value of the interface , So what we should force is iface.data.
To do that , We need to put context First convert to iface, And get the data:

package main

type 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
}

This time , We finally got the data .

perfect

Next , We need to take context All in key value Get out :

package main

func 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)
}

Called recursively , We can easily traverse all context, meanwhile golang Medium nil At the bottom is actually a int Of 0, That's why we put nil be called Zero value . therefore , The exit condition of recursion is also very simple , Is that when iface Medium data by 0 when , That means we've found context The last one in .

Okay , That's all , If you want to get the specific implementation and demo Example , You can come to my github Found on the , Thanks for reading .

版权声明
本文为[r1chard]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/01/20210123201543731z.html

随机推荐