conclusion
The meaning of context is to maintain a unified “context” between function calls. Share information in context by saving state.
So-called “functions” such as timeout cancellation are also “states”.
And context.context happens to have concurrency security. Therefore, it can be widely used in various functions and methods.
This problem can be illustrated from several aspects.
State sharing
Here’s an example of what state sharing means:
Fn1 creates the “state” I of type int and passes it on.
func fn1(a) {
var i int
fn2(i)
}
Copy the code
Because FN4 needs state I. So even though Fn2 and FN3 don’t need the “state” themselves, they still need to declare and pass the “state” in the parameter.
func fn2(i int) {
fn3(i)
}
func fn3(i int) {
fn4(i)
}
func fn4(i int) {
print(i)
}
Copy the code
Fn1 -> Fn2 -> FN3 -> FN4 is linked like a chain by the state I.
There seems to be no problem so far. All parts of the code work just a little bit wordy.
But what if one day the requirements change and FN4 no longer needs the “state” of I? All code from fn4 to fn2 needs to remove the I state from the parameter declaration. This kind of butterfly effect of code changes is undoubtedly huge.
In order to solve the problem of “changing the parameter list of the last function, but changing all the functions of the whole project”. The behavior of “getting state” can be abstracted separately, with each function taking arguments from a common “parameter center.” This eliminates the need to change the declaration of a function when the requirements change.
It can be done like this:
- Create the parameter center object CTX.
- Modify the function to declare CTX as an argument.
- Put “state”
i
Share to CTX. - Gets the state from the parameter center.
type Context map[string]interface{}
func fn1(a) {
var ctx = make(Context)
ctx["i"] = 1
fn2(ctx)
}
func fn2(ctx Context) {
fn3(ctx)
}
func fn3(ctx Context) {
fn4(ctx)
}
func fn4(ctx Context) {
var i = ctx["i"]. (int)
print(i)
}
Copy the code
In this way, functions do not need to modify their parameter declarations, no matter how many arguments they need or how their requirements change. And a “parameter relay” like Fn2 or FN3 will not be modified by some “grandchild” whose function needs change.
So far, the main purpose of the Context object has been explained.
Process control
Here’s another example of context flow control, such as timeout cancellation:
We know that channels can be used to pass information between coroutines.
var ch = make(chan int)
go func(a) {
ch <- 1} ()var i int = <-ch
print(i)
Copy the code
You also know that “channel closure” is also passed as a message.
var ch = make(chan int)
go func(a) {
ch <- 1
close(ch)
}()
a, ok := <-ch // 1 true
b, ok := <-ch // 0 false
Copy the code
Can we create a channel that does not handle data, but only closed signals? A “channel closed” is passed between coroutines as a message.
var ch = make(chan struct{})
go func(a) {
time.Sleep(time.Second) / / sleep 1 s
close(ch)
}
<-ch // will block for 1s
Copy the code
So a channel like ch := make(chan struct{}) can be used to pass a signal between coroutines that says “end”. By close(ch), coroutine A can control coroutine B without touching coroutine B.
State encapsulation
As mentioned above, remote control between coroutines can be implemented using ch of type channel.
If “channel ch” is a “state”, put it in Fn1 -> Fn2 -> FN3 -> FN4. Wouldn’t fN1 be able to control FN4 without touching it?
It can be done like this:
- Create a “state” ch of type channel at fn1.
- Share the state CH to the Parameter center.
- Listen on ch in FN4.
In this way, FN1 can remotely control FN4 via CH.
But there is a small problem with this: in the call chain of fn1 -> Fn2 -> Fn3 -> FN4. Fn2 and FN3 can also obtain the state CH from the parameter center. Fn2 and FN3 can terminate a channel without fN1’s consent.
Other functions have full access to the state created by FN1. It’s like “your son calls the old wang next door dad”. Still pretty cool
Therefore, FN2 and FN3 do not have full control over state CH.
The solution is simple. The state FN of type func is shared in the parameter center. Encapsulate ch and share only the “function” of “listen to end message”.
type Context map[string]interface{}
type Done func(a) (< -chan struct{})
func fn1(a) {
var ch = make(chan struct{})
/ / function
var fn = func(a) (< -chan struct{}) {
return ch
}
var ctx = make(Context)
ctx["fn"] = fn
go fn2(ctx)
close(ch)
}
func fn2(ctx Context) {
// Even if fn is intercepted here, ch cannot be prematurely terminated
// Because ch is deflected by fn
fn3(ctx)
}
func fn3(ctx Context) {
fn4(ctx)
}
func fn4(ctx Context) {
fn := ctx["fn"].(Done)
<-fn()
print("done")}Copy the code
conclusion
To sum up:
- Create a “parameter center” to share state and decouple in the chain of function calls.
- Sharing CH in the parameter center enables cross-coroutine control.
- Sharing “functionality” in the parameter center enables state encapsulation.
Finally, if the process of calling FN1 -> Fn2 -> FN3 -> FN4 is compared to a river. Fn1 can then be called “upstream” of FN4.
The “parameter center” CTX, passed between “upstream” FN1 and “downstream” FN4, is the role that controls the entire “context”.