This is a creation in Article, where the information may have evolved or changed.
Go to the context of the problem
2017-05-29
Recently triggered by the context of a bug hole is not light, so reflect on the go to the context of the problem.
Context is an implicit constraint, no detection
If we write a function, such as:
func f(a int, b []byte) {}
We know what parameters it needs, and the compiler is going to help me do the checking when I call
f(3, "sdfsdf")
It will get an error.
However, if it is a context, it becomes an implicit constraint, and the compiler does not check for us, such as:
func f(ctx context.Context) { a := ctx.Value("a").(int) b := ctx.Value("b").([]byte)}
The value function has no guarantee that the compiler will not check whether the arguments passed in are reasonable. However, the invocation of f in what context is not deterministic, so the detection is moved to the runtime to do so.
Now the function f has an implicit constraint, it needs to pass A and b two parameters from the context, which can not be reflected in the signature of function F. If I look at a function, see its signature is useless, still have to read its implementation, this is not nonsense!
Lock contention for context
Context is a layer down, if the whole is to use the same pass down the context, there will be a problem: lock contention.
select { case <-context.Done():}
Everyone calls the done function on the same object, and the channel operation is eventually locked. This is a problem found in the ETCD project, they changed and we changed. In the goroutine, generally do not use the original context, but a new context, the original context as the parent context. So different goroutine will not rob the same lock.
This function is generally used context.WitCancel() :
go func() { ctx, cancel = context.WithCancel(ctx) doSomething(ctx) cancel()}
When you call Withcancel, you get a new sub-context, and a cancel function. The child CTX receives a signal when the done function of the parent context or Cancel is invoked to receive the done signal.
Cancel is required to invoke, which causes the context to release the corresponding resource. The bug at the beginning is that this place is being dug up: After writing the code there is a hypothetical constraint that the dosomething operation is a synchronous one, and when it returns, the corresponding context is over.
Then, our code in the dosomething inside the function is very deep (a tune b,b tune C,c D), there is a open goroutine asynchronous operation, so the silly. The asynchronous operation was not completed and was cancel.
But the problem is very difficult to find, why? No problem was seen because of the code snippets that looked at two places alone. The above code is no problem, as long as the dosomething is a synchronous operation on the line. The logic of the dosomething is no problem, it has other functions, and the other functions continue to be deeper, but there is no restriction on the operation of asynchronous operations.
Do not save any context as a member variable
The standard usage of the context is to produce one at a time, and then pass it down one layer at a later level. Note that it is forbidden to capture the context for storage. Do not save any context as member variables, and do not reuse them.
For example, I want to make a sender object, which has a send method. Then I can't save ctx at new and use it when I send it:
func NewSender(ctx context.Context) *sender { return &sender { ctx: ctx, }}func(s *sender) Send() { grpc.XXX(s.ctx)}
If you call a library and it needs to pass a context, you should give it the contextual, if not, you can pass it context.Background() , but do not, as above, save the context when creating the object, and use it when the object's method calls.
The correct posture should not see the context being saved to any member variable.
The context is essentially a dynamic scope
It says don't save the context. Let's take a look at the nature of the problem:
obj = new Object(ctx)obj.method(ctx)
Excuse me, is this the same context? No! The context at which a time is created, and one that is the runtime context. Actually write correctly, they are like this:
obj = new Object(ctx1)obj.method(ctx2)
Then save the ctx1 and give it to ctx2, of course not.
A few times after the pit will feel the context is very difficult to use. I thought about it, but the problem is similar to the dynamic scope. In the modern mainstream programming language, there is no one in the dynamic scope, and people are mostly accustomed to lexical scope, so it is difficult to accept the thinking.
Just say it. Dynamic scopes:
func f() { a := 3 func g() int { return a }}
In a lexical-scoped language, the g() returned result is 3 regardless of where it is called. In a dynamic-scoped language, the behavior is completely beyond inference:
a := 7g() // 这里返回的是7,a的值是看运行时绑定的,而不是声明时a := 3g() // 这里返回的是3
When you see that the function needs parameters is a context, can be in the context of each run is different, just look at the declaration and there is no information, is not much like dynamic scope?