4

Hello everyone, I am fried fish.

In daily work, if we can understand the Go language memory model, it will bring a very big effect. In this way, when looking at some extreme situations or abnormal interview questions, you can understand many root causes under the performance of the program.

Of course, it is impossible to finish the Go memory model with an ordinary article. Therefore, this article today, to focus on planning for everyone happens to explain the principles of the before-Go language this one detail.

Start sucking, and fried fish to uncover his mystery!

What is the memory model definition

Since we want to understand the happens-before principle, we must first know what The Go Memory Model (Go Memory Model) defines. 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: "When reading a variable in a goroutine, it can be guaranteed to observe the value generated by writing the same variable in different goroutines".

This is a major prerequisite for learning follow-up knowledge.

what happens-before

Happens Before is a technical term that has no direct relationship with the Go language, that is, it is not unique. In the vernacular, its definition is:

In a multithreaded program, suppose there are two operations A and B. If A happens-before B, then the impact of A happens-before B will be visible to the thread executing B.

A does not necessarily happen-before B

From the happens-before definition, we can think the other way around. That is:

In the same (same) thread, if both operations A and B are performed, and the declaration of A must precede B, then A must happen-before B.

Take the following Go code example:

var A int
var B int

func main() {
 A = B + 1  (1)
 B = 1      (2)
}

The code is in the same main goroutine, and the global variable A is declared before the variable B.

In the main function, the code line (1) is also before the code line (2). So we can conclude that (1) must be executed before (2), right?

The answer is: wrong, because A happens-before B does not mean that operation A must happen before operation B.

In fact, in the compiler, the actual execution sequence of the above code in assembly is as follows:

 0x0000 00000 (main.go:7) MOVQ "".B(SB), AX
 0x0007 00007 (main.go:7) INCQ AX
 0x000a 00010 (main.go:7) MOVQ AX, "".A(SB)
 0x0011 00017 (main.go:8) MOVQ $1, "".B(SB)
  • (2): Load B to register AX.
  • (2): Perform B = 1 assignment, and execute INCQ auto-increment in the code.
  • (1): Add 1 to the value in register AX and assign it to A.

Through the above analysis, we can know. The code line (1) precedes (2), but it is true that (2) is executed earlier than (1).

So does this mean that the happens-before design principle is violated, after all, this is an operation in the same thread, and the Go compiler has a bug?

In fact, it is not, because the assignment of A has no effect on the assignment of B. So it did not violate the happens-before design principle.

Happens-before in Go

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 referential variable used for demonstration purposes.
  • Read r: stands for read operation.
  • Write w: represents a write operation.

definition

Under the following two conditions, the reading r of variable v is allowed to observe the writing w of v:

  1. r did not happen before w.
  2. No other w'written to v occurs after w but before r.

In order to ensure that the read r of the variable v observes a specific write w to v, make sure that w is the only write that is allowed to be observed by r.

Therefore, if the following two points are both true, it can be guaranteed that r can observe w:

  1. w occurs before r.
  2. Any other writes to the shared variable v occur before w or after r.

This seems rather jerky. Next, we will use the specific channel example in "The Go Memory Model" to further explain it, and it will be better to understand.

Go Channel instance

In the Go language, it is recommended not to communicate through shared memory; on the contrary, memory should be shared through communication:

Do not communicate by sharing memory; instead, share memory by communicating.

Therefore, Channel is a very common syntax in Go projects. In principle, it needs to comply with:

  1. The transmission on a channel occurs before the corresponding reception of the channel is completed.
  2. The closing of the channel occurs before the reception, and a zero value is returned because the channel is closed.
  3. The reception of an unbuffered channel occurs before the channel's transmission is completed.
  4. On a channel with a capacity of C, the kth reception occurs before the k+Cth transmission of the channel is completed.

Next, according to these four principles, we will give examples one by one for learning and understanding.

Example 1

Go channel example 1. What do you think is the output result? as follows:

var c = make(chan int, 10)
var a string

func f() {
 a = "炸煎鱼"   (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 the normal output of "fried fish" for the following reasons:

  • (1) happens-before (2) 。
  • (4) happens-after (3)。

Of course, the last (1) operation of writing variable a must happen-before in (4) print method, so "fried fish" is output correctly.

It can be satisfied that "the transmission on a channel occurs before the corresponding reception of the channel is completed".

Example 2

Mainly to ensure the behavior when closing the pipeline. It is only necessary to replace c <- 0 with close(c) in the previous example to generate a program with the same behavior guarantee.

It can be satisfied that "the closing of the channel occurs before the reception, and a zero value is returned because the channel is closed".

Example 3

Go channel example 3. What do you think is the output result. as follows:

var c = make(chan int)
var a string

func f() {
 a = "煎鱼进脑子了"    (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 the normal output "fried fish in the brain", the reason is as follows:

  • (2) happens-before (3)。
  • (1) happens-before (4)。

It can satisfy that "the reception of an unbuffered channel occurs before the transmission of the channel is completed".

If we change the unbuffered to make(chan int, 1) , which is the buffered channel, the normal output cannot be guaranteed.

Example 4

Go channel example 4. This program starts a goroutine for each entry in the work list, but the goroutine uses the channel to coordinate to ensure that only three work functions are running each time.

code show as below:

var limit = make(chan int, 3)

func main() {
 for _, w := range work {
  go func(w func()) {
   limit <- 1
   w()
   <-limit
  }(w)
 }
 select{}
}

It can satisfy that "on a channel with a capacity of C, the kth reception occurs before the k+Cth transmission of the channel is completed".

Summarize

In this article, we have provided a basic explanation of the happens-before principle. At the same time, the actual happens-before and happens-after scenes in the Go language are displayed and explained.

In fact, in daily development work, the happens-before principle has basically penetrated the subconscious mind, just like design patterns. It will be applied unknowingly, but if we want to further study and understand the memory model such as Go language, we must understand this basic idea.

Have you noticed this problem? Welcome everyone to leave a message and discuss!

If you have any questions, welcome feedback and communication in the comment area. The best relationship between . Your likes is the biggest motivation for the creation of fried fish

The article is continuously updated, and you can read it on search [the brain is fried fish], this article 161795b6aec1d7 GitHub github.com/eddycjy/blog has been included, learn the Go language, you can see Go learning map and route .

refer to

  • The Go Memory Model
  • Go memory model & Happen-Before (1)
  • GoLang memory model
  • Golang happens before & channel
  • Go memory model

煎鱼
8.4k 声望12.8k 粉丝