introduction
In daily work, if we can understand the Go language memory model, it will have a very big effect. In this way, when looking at some extreme situations or abnormal interview questions, you can understand many of the fundamental reasons for the performance of the program.
Of course, it is impossible to finish the Go memory model with an ordinary article. Therefore, today's article will focus on everyoneExplain the happens-before principle of Go languageThis 1 detail.
What is the memory model definition
Since we want to understand the happens-before principle, we must first know what the Go Memory Model is defined. The official explanation is as follows:
The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.
The Go memory model stipulates that "when reading a variable in a goroutine, it is guaranteed to observe the value generated by writing to the same variable in different goroutines."
This is a major prerequisite for learning subsequent knowledge.
What is happens-before
Happens Before is a professional term that has no direct relationship with Go language, that is, it is not unique. In plain language, its definition is:
In a multithreaded program, assuming that there are two operations A and B, if operation A occurs before operation B (A happens-before B), the impact of operation A on memory will be visible to the thread that executes B.
A does not necessarily happens-before B
From the perspective of happens-before definition, we can think about it the other way around. That is:
In the same (same) thread, if both A and B operations are performed, and A's declaration must be before B, then A must occur before (when) B.
The following example of Go code:
var A int var B int func main() { A = B + 1 (1) B = 1 (2) }
The code is declared in the same main goroutine, with the global variable A being declared before variable B.
In the main function, line of code (1), also before line of code (2). So we can conclude that (1) will definitely be executed before (2), right?
The answer is: wrong, because A happens-before B does not mean that operation A must occur before operation B.
In fact, in the compiler, the actual execution order of the above code in assembly is as follows:
0x0000 00000 (:7) MOVQ "".B(SB), AX 0x0007 00007 (:7) INCQ AX 0x000a 00010 (:7) MOVQ AX, "".A(SB) 0x0011 00017 (:8) MOVQ $1, "".B(SB)
- (2): Load B to register AX.
- (2): Perform B = 1 assignment, and execute it as INCQ autoincrement in the code.
- (1): Add 1 to register AX median value and assign it to A.
Through the above analysis, we can learn. Before line of code (1) before (2), but does (2) execute earlier than (1).
So does this mean that it violates the design principles of happens-before. After all, this is an operation in the same thread. There is a bug in the Go compiler?
In fact, this is not the case, because the assignment to A has no effect on the assignment to B. So it does not violate the happens-before design principles.
happens-before in Go language
In "The Go Memory Model", a clear language definition of Happens Before in the Go language is given.
The following terms will be used in the introduction:
- Variable v: A reference variable for example demonstration.
- Read r: represents reading operation.
- W: represents writing operation.
definition
Under the following two conditions, read r is allowed to observe the writing of v to v:
- r did not happen before w.
- No other w' that writes to v happens after w but before r.
To ensure that the read of the variable v r observes a specific write to v, make sure that w is the only write that allows r to observe.
Therefore, if both of the following points are true, it is possible to ensure that r can observe w:
- w occurs before r.
- Any other writes to the shared variable v occur before w or after r.
This looks a bit awkward. Next, we will further explain it with the specific channel example in "The Go Memory Model", which will be better understood.
Go Channel Example
In Go language, it is recommended not to communicate through shared memory; instead, memory should be shared through communication:
Do not communicate by sharing memory; instead, share memory by communicating.
Therefore, in Go projects, Channel is a very common syntax. In principle, it needs to be followed:
- A sending on a channel occurs before the corresponding reception of that channel is completed.
- The shutdown of channel occurs before receiving, and returns a zero value because the channel is closed.
- The reception of an unbuffered channel occurs before the transmission of that channel is completed.
- On a channel with capacity C, the kth reception occurs before the kth + Cth transmission of the channel is completed.
Next, based on these four principles, we give examples one by one for learning and understanding.
Example 1
Go channel Example 1, what do you think the output is the result. as follows:
var c = make(chan int, 10) var a string func f() { a = "Fried fish" (1) c <- 0 (2) } func main() { go f() <-c (3) print(a) (4) }
Is the answer an empty string?
The final result of the program is to output "fried fried fish" normally, for the following reasons:
- (1) happens-before (2) 。
- (4) happens-after (3)。
Of course, the operation of writing variable a must happens-before in (4) the print method, so the "fried fish" is correctly output.
Able to satisfy "the transmission on a channel occurs before the corresponding reception of the channel is completed".
Example 2
Mainly, it ensures behavior when closing the pipeline. Just need to replace it in the previous examplec <- 0
becomeclose(c)
It can produce procedures with the same behavior guarantee.
Able to satisfy "channel closing occurs before receiving, and returns a zero value because the channel is closed".
Example 3
Go channel Example 3, what do you think the output is the result. as follows:
var c = make(chan int) var a string func f() { a = "Frying fish into the brain" (1) <-c (2) } func main() { go f() c <- 0 (3) print(a) (4) }
Is the answer an empty string?
The final result of the program is normal output of "fried fish into the brain", for the following reasons:
- (2) happens-before (3)。
- (1) happens-before (4)。
Able to satisfy "the reception of an unbuffered channel occurs before the transmission of the channel is completed".
If we change the unbuffered tomake(chan int, 1)
, that is, a channel with buffering, cannot guarantee the normal output "fried fish into the brain".
Example 4
Go channel Example 4, this program starts a goroutine for each entry in the work list, but goroutine is coordinated using channel to ensure that at most three work functions are running at a time.
The code is as follows:
var limit = make(chan int, 3) func main() { for _, w := range work { go func(w func()) { limit <- 1 w() <-limit }(w) } select{} }
It can meet "on a channel with capacity C, the kth reception occurs before the kth + C transmission of the channel is completed."
Summarize
In this article, we provide a basic explanation of the happens-before principle. At the same time, the actual happens-before and happens-after scenes in Go language are combined into the presentation and explanation.
In fact, in daily development work, the happens-before principle has basically penetrated into the subconscious mind, just like the design pattern. It will be applied unconsciously, but if we hope to further study and understand memory models such as Go, we must have some understanding of this basic concept.
refer to
Golang memory model detailed explanation (I)
Go memory model
The above is a detailed content of a memory model detail that must be known for Go language development. For more information about the details of Go language memory model, please pay attention to my other related articles!