Original link: Interviewer: is it thread safe for Context to carry data?
preface
Hello, everyone. I'm asong. Recently, I saw an interesting eight part essay in a group. The question is: is the value carried in context thread safe? In fact, this question is to investigate the interviewer's understanding of the implementation principle of context. If you don't know the implementation principle of context, it's easy to answer this question incorrectly. Therefore, in this paper, we use this question to re understand the implementation principle of context carrying value.
Is it thread safe for context to carry value?
First of all, the answer is that context itself is thread safe, so the value carried by context is also thread safe. Write a simple example to verify:
func main() { ctx := context.WithValue(context.Background(), "asong", "test01") go func() { for { _ = context.WithValue(ctx, "asong", "test02") } }() go func() { for { _ = context.WithValue(ctx, "asong", "test03") } }() go func() { for { fmt.Println(ctx.Value("asong")) } }() go func() { for { fmt.Println(ctx.Value("asong")) } }() time.Sleep(10 * time.Second) }
The program runs normally without any problems.
However, context has no type restrictions on the data carried, so any data type is carried by context. When the carried data type is pointer type, it is not thread safe. Take an example:
func main() { m := make(map[string]string) m ["asong"] = "Golang DreamWorks" ctx := context.WithValue(context.Background(), "asong", m) go func() { for { m1 := ctx.Value("asong") mm := m1.(map[string]string) mm["asong"] = "123213" } }() go func() { for { m1 := ctx.Value("asong") mm := m1.(map[string]string) mm["asong"] = "123213" } }() time.Sleep(10 * time.Second) }
Operation results:
fatal error: concurrent map writes goroutine 18 [running]: runtime.throw({0x1072af2, 0x0}) ......
Why is thread safe?
The context package provides two ways to create a root context:
- context.Backgroud()
- context.TODO()
Another four functions are provided to derive from the parent context, in which the WithValue function is used to derive the context and carry data. Each call to the WithValue function will derive a new child context based on the current context. The interior of WithValue mainly calls the valueCtx class:
func WithValue(parent Context, key, val interface{}) Context { if parent == nil { panic("cannot create context from nil parent") } if key == nil { panic("nil key") } if !reflectlite.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} }
valueCtx is structured as follows:
type valueCtx struct { Context key, val interface{} }
valueCtx inherits the parent Context. This is an inheritance implementation method using anonymous interface. Key and val are used to store the carried key value pairs.
Through the above code analysis, we can see that the added key value pair is not directly added on the original context structure, but takes the context as the parent node, recreates a new valueCtx child node, and adds the key value pair to the child node to form a context chain.
The process of obtaining the key value is also called up layer by layer until the final root node. If the key is found in the middle, it will return. If not, it will find the final emptyCtx and return nil.
Draw a picture to show:
Summary: the key value added to the context is a chain, and new context will be derived continuously. Therefore, the context itself is immutable, so it is thread safe. However, if the data we carry is of pointer type, there is still a risk of thread insecurity.
summary
This article mainly wants to take you to review the implementation principle of context. In the interview, interviewers like to ask questions implicitly, so we need to have solid basic skills. If we are not careful, we will fall into the trap of the interviewer. Be careful everywhere
Well, that's the end of this article. I'm asong. I'll see you next time.
Welcome to the official account: Golang DreamWorks.