Go's memory model

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

The Golang website has a separate page that describes--go's memory model. Me here is to translate it, and then with a few small programs, and then add (personal) instructions. I don't know much about certain things, bright some of them are vague or even strange. The translation level is limited, do not scold me,o__o "...

multithreaded/Concurrent programs sharing data is both a blessing and a hassle. There is no problem with the "read" of the shared data, and the problem is on "write", such as two processes writing the same memory value. High-level languages write a memory variable such as a = a+1; is often not an atomic operation (indivisible), that is, the operation will be split into multiple steps (typically from the memory load to register, in registers + 1, from the Register store to memory), try to think if the two threads of the write multi-step intersection, the result will be? For example, threads f and g will be a = a+1, respectively; Supposedly, a will increase by 2 after the end of F and G, but that may not be the case. This is one of the places where multithreading is troublesome. Multithreading Trouble Place two, imagine two thread write, a thread read the same A, then read the result is the original result, or +1 results, or +2 results?

The idea for the first question above is locking lock (or mutex mutex) to ensure consistency and ensure that the write operation is "indivisible". For the second problem, which is the control of the order of execution between threads, it is common to use semaphores/condition variables to limit their sequential execution. Mutually exclusive access and sequential execution, collectively referred to as thread synchronization issues. Multithreaded programming is a headache is ya, o__o "... It's a little bit serious, =_=. In short, the general multithreading for the sharing without processing, the value of the data is completely "resigned to fate", but "sharing is shared", a thread in the T moment will be the value of v written in, the subsequent operation of all the threads are on the basis of V.

Go is a little special. Go multi-threading, or go thread, or say Goroutine, is a strange thing, although the data is shared, but if not synchronized with the system, this sharing is not possible to see each other, O__o "... As an example, shared a (int) variable, go f (); Go g (); If the F and G threads are created in main, the value of a is +1, but there is no synchronization mechanism, you may see this change in main (+1 or +2), or you may not see the change (+0), or even the implementation can be: F and g are not created, because they do not have a real Meaning, no synchronization can be considered other threads do not care about this change, o__o "... Well, it's the understanding of me, or the official version. (If you see this, the bright go and other language models are similar, you actually see the big difference behind them.) )

Version: March 6, 2012

    • Simple description
    • occurred before
    • Synchronous
      • Initialization
      • Goroutine Create
      • Goroutine Destruction
      • Pipeline communication
      • Lock
      • Once
    • Wrong synchronization

Simple description

This Go's memory model is to illustrate the circumstances in which the value of a variable written in a goroutine is perceived by another goroutine.

occurred before

In a single goroutine, the behavior of reading and writing behaves like the order specified in the (as if) program, which implies that a compiler or processor can rearrange the read and write order of variables, as long as the order is not violated by the language specification definition. [This is better understood, ...; a = 1; b = 2; .... There is no need to assign a value of a before the assignment B, these two statements even if reversed does not affect the performance behind, of course, may also be executed simultaneously. ] because there may be reordering, the order seen in one goroutine may not be the same as another goroutine. [This all can, O__o "...? For example, a goroutine is executed in a = 1; b = 2; Another possibility is to see that the value of B is updated before the value of a. [Knowing this, the last few examples with bugs will actually understood a lot.] ]

To illustrate the reading and writing, we define a pre -existing partial-order relationship that [satisfies the reflexive, inverse-symmetry and transitive relations called the partial-order relationship, the typical partial-order relationship is a "less than equals" relationship, in fact the elders-juniors relationship is more like a partial-order relationship, because the partial-order relationship does not require any two elements to ], this partial-order relationship reflects the execution of operations in the Go program. If the event E1 occurs before E2 , we also say thatE2 occurs after E1 . [There may be some ambiguity here, I understand that before the occurrence of E1 before E2 occurs, and the end of E1 is not before the beginning of E2, this does not know. In addition, if E1 is not occurring either before E2 or after E2 , we say that E1 and E2 are concurrent. [Perhaps u would be surprised, E1 not either before E2, not either after E2? Does it have to happen at the same time? It seems that it is not so understanding, but the concurrent two threads, no prior to and after this relationship, the partial order relationship does not require any two of the amount has a relationship. ]

In a single goroutine, this sequential relationship that occurred earlier is the order in which the program is described.

As long as the following two conditions are met, a read R access to another write W result is allowed: ["Allow", allowed, means, u access to, but can also not access to, o__o ".... ]

    • Read R does not occur before W;
    • No other write W ' located after W, R before;

In order to ensure that the result of a read R is exactly one write W, you need to ensure that R can see only one w, that is, to meet the following two conditions:

    • W occurs before R;
    • The other write W ' is either before W or after R;

The following conditions are stricter than the one above, which requires no other write with W or R concurrency (either before W or after R). [There are a couple of conditions above that are possible: a r after W, then a W ' with W concurrent and W ' without and W ', the other is R and W concurrency, and of course there may be other w ' and w/r concurrency. ]

There is no concurrency in a single goroutine, so the above two pairs of conditions represent the same meaning: R can see the result of the W closest to it. Then when there are multiple goroutine accessing the same V, we have to use synchronous events to establish this previously occurring relationship so that the read can see the results of the write that you want to see.

The V variable uses a value of 0 to initialize in the memory model equivalent to write once.

When reading and writing a multi-word (word) value, it is equivalent to multiple word operations, and the order of the multiple operations is unspecified.

[Bright must say two more sentences, the above two statements, really make people dizzy, bright a bit tongue twister taste, are used by the negative, rather than affirmation, very strange. A is not in front of B, it does not mean that A is behind B, they can be tied together. In this case, the first argument, that is, for two parallel threads, is whether one thread can read the data written by another thread, or not, of course. The second argument, because R occurs after W, there is no other w ' in front of R, and R is not the same as W ', so r reads the value must be w write the value, O__o ".... This is illustrated in conjunction with a graphic. ]

 single Goroutine Case: (labeled R read,w is write, all operations on value)--w0----R1-W1----W2-----r2----R3----------> here Not only is a partial-order relationship, but a good-order relationship, all the r/w are comparable; double Goroutine case:--w0----R1-r2----W3-----W4----R5---------->--W1 -----W2----R3-----R4----W5----------> on a single thread, and then said to two threads, R1 and W2 Order is God horse? Even if "Human Time" W2 > R1, in Go, W2 is not first/later in R1, the standard is that both concurrency; concurrency r/w For example, what is the result of R3 reading? It may be the value of the preceding W2, or the value of the W3 above, or the value of W4, while the value of R5 may be W4, W1, W2, and the intersection of double W5:-goroutine----r0------------|------ --------R2---------------------|--------------W5----------R5---------->--W1-----W2-----------|---------R3-- ---r4----W4--------|-----------> Now adds two intersections--using | Place, in this case, R3 is after the R1, before W5, R2 in front of the write is W2, and concurrency has W4, so the value of R2 is indeterminate, can be W2, can also be W4!! And R4 is written in front of the W2, and it is not written in parallel, so R4 read the value is W2!! 

In this case, the relationship seems a little clear, if not synchronized control, all the threads are "parallel" concurrency, so that the main function of the thread is meaningless, because the main function can think that they do not have a relationship with me!! Only with the synchronization control, such as locks, such as pipelines, so that the thread between the "knot", they have a pre/post in the order, but in the two "nodes" between the part, also has no relationship.

Synchronous

Initialization

The initialization of the program is performed in a single goroutine, and this goroutine may create additional goroutine that are executed concurrently.

If a package P imports the packet Q, then the init function of Q will occur before the init of P.

The start of the Main.main function occurs after all INIT functions have completed .

Creation of Goroutine

Go statement to start a new goroutine, which occurs before the start of goroutine execution.

For example, the following example:

    1. var a string

    2. Func f () {

    3. Print (a)

    4. }

    5. Func Hello () {

    6. A = "Hello, world"

    7. Go f ()

    8. }

The call to Hello will output "Hello,world", the output may be at some later time, when the output might be the Hello has exited.

Destruction of Goroutine

A goroutine exit is not guaranteed to occur before any event in the program. For example, the following program:

    1. var a string

    2. Func Hello () {

    3. Go func () {a = "Hello"} ()

    4. Print (a)

    5. }

The assignment to A is not followed by any synchronization events, so there is no guarantee that a change can be seen in other goroutine. In fact, a crazy compiler can erase the entire GO statement. [Nor does it violate the preceding provisions.] ]

If the execution of a goroutine can be perceived by other goroutine, then use a synchronization mechanism, such as a lock or channel communication, to establish a relative order.

Pipeline communication

Pipeline communication is the main method for synchronizing in Goroutine. One send for each pipe corresponds to a receive for the pipe, but usually the send and receive operations are in different goroutine.

The send operation for the pipeline occurs before the corresponding receive operation completes.

Program:

    1. var c = make (chan int, 10)

    2. var a string

    3. Func f () {

    4. A = "Hello, world"

    5. C <-0

    6. }

    7. Func Main () {

    8. Go f ()

    9. <-c

    10. Print (a)

    11. }

The result is that the "Hello,world" is definitely output, because the write to a occurs before the send data to pipe C, and the sending of pipe C occurs before the receipt of C, more before print a.

A close operation for a pipe occurs before the pipe gets a value of 0, and the pipe receives a 0 value after the pipe is closed.

For the above example, using Close (c) to replace C <-0, the program results are the same.

For a non-buffered pipeline, the receive operation occurs before the send operation completes.

The following procedure compares the order in which the send and receive statements are exchanged, but using the unbuffered pipeline:

    1. var c = make (chan int)

    2. var a string

    3. Func f () {

    4. A = "Hello, world"

    5. <-c

    6. }

    7. Func Main () {

    8. Go f ()

    9. C <-0

    10. Print (a)

    11. }

The result of the program is also "Hello,world", because the write to a occurs before C is received, before the dispatch of C occurs before the print is completed. If the pipeline is replaced with a buffer pipe, such as C = Make (chan int, 1), then the result of the program is very different, may output "Hello,world", may be empty string, may be some other things, even the program will crash, O__o "...

Lock

The sync package implements two data types on the lock, sync. Mutex and sync. Rwmutex. [Mutex mutex is exclusive, can only lock once, unlock once, then can continue lock otherwise blocked. Read-write mutex reader-writer mutex is all reader sharing a lock or a writer exclusive a lock, if a reader lock to lock, other reader can lock but writer can't lock 。 ]

for sync. A Mutex or sync. For Rwmutex types of variable mutexes, assume n < m, for mutexes. The nth invocation of Unlock () is in the mutex. The first Call of Lock () occurs before the return of the M. [For a mutex, lock, the second lock will block, only unlock to continue lock, that's what it means. But what about unlock a mutex without lock? Error! ]

Program:

    1. var l sync. Mutex

    2. var a string

    3. Func f () {

    4. A = "Hello, world"

    5. L.unlock ()

    6. }

    7. Func Main () {

    8. L.lock ()

    9. Go f ()

    10. L.lock ()

    11. Print (a)

    12. }

The result of the program is also "Hello,world". The assignment for a occurs before the L.unlock () call, L.unlock () occurs before the second call to L.lock () is returned, and the second call to L.lock () occurs before print.

for sync. The Rwmutex is a variable of type mutex for each mutex. Rlock is called, if it is in nth-time mutex. Unlock called the mutex after the call. Rlock, then the mutex. The first n+1 call to Lock is on the mutex. Occurs after Runlock. [a bit around, but the effect seems to be that if the writer lock unlock after a number of reader lock, then the next time to obtain a writer lock in front of all reader unlock, o__o "...]

Once

A security mechanism is provided in the sync package to enable multiple goroutine to be initialized only once, using the Once type. Multiple threads can execute once. Do (f), but only one can run F (), and the other calls will be blocked until F () returns.

For a single invocation of f () in all once. Do (f) occurs before returning.

The following program:

    1. var a string

    2. var once sync. Once

    3. Func Setup () {

    4. A = "Hello, world"

    5. }

    6. Func Doprint () {

    7. Once. Do (Setup)

    8. Print (a)

    9. }

    10. Func Twoprint () {

    11. Go Doprint ()

    12. Go Doprint ()

    13. }

Calling Twoprint will have two "Hello,world" output, and the first call to Twoprint runs a setup.

Wrong synchronization

[I've had a few questions about these programs before, and now it's much better to understand.] The threading model of Go is different from ptheads (C language), C + +, Java and so on. ]

Note: Reading to a variable v if you perceive a concurrent goroutine write to V, even if this happens, does not mean that reading the back of the read can be perceptible to write the front, o__o "... For example, the following program:

    1. var a, b int

    2. Func f () {

    3. A = 1

    4. b = 2

    5. }

    6. Func g () {

    7. Print (b)

    8. Print (a)

    9. }

    10. Func Main () {

    11. Go f ()

    12. G ()

    13. }

The results of the program may output 2 and 0. [output 00, 21, 01 presumably are agreed, the output 20 echoes the different threads mentioned earlier to see that the program's execution order may be different. ]

Such a simple fact that many of the customary practices used in previous or other languages no longer apply.

Two check locking (double-checked locking) is an attempt to avoid the overhead of synchronization, for example, the Twoprint program can be rewritten as follows, although it is no longer correct:

    1. var a string

    2. Var done bool

    3.  

    4. li>

      Func setup ()  {

    5.         a =  "Hello, World"

    6. li>

              done = true

    7. }

    8.  

    9. Func doprint ()  {

    10.         if !done {

    11.           &NBSP ;     once. Do (Setup)

    12.         

    13.         prin T (a)

    14. }

    15.  

    16. Func twoprint ()  {

    17.         go doprint ()

    18.         go doprint ()

    19. }

We will not guarantee that you see the done change in Doprint (that is, to ture) also means you can see a change (into "Hello,world"). The version here may output an empty string instead of "Hello,world" [that is, the output of the backward one may not be expected ...].

There is also an incorrect idiom that is busy waiting for a value, such as:

    1. var a string

    2. var done bool

    3. Func Setup () {

    4. A = "Hello, world"

    5. Done = True

    6. }

    7. Func Main () {

    8. Go Setup ()

    9. For!done {

    10. }

    11. Print (a)

    12. }

Similar to the previous statement, there is no guarantee that the done change can be seen in main and that a is also visible, so the program may also output an empty string. In a worse case, because there is no synchronization control event in two threads, the done changes are not necessarily perceived by main, which means that the main function may die forever, o__o "... [For people who use other thread libraries, they believe that one day (unless the process abort the end of the world) Setup executes, this time main will see a and done changes, and then main ends ...].

In this direction, we can drift away to see a more obscure program:

    1. type t struct {

    2.         msg string

    3. }

    4.  

    5. var g *t

    6.  

    7. func setup ()  {

    8.         t := new (t)

    9.         t.msg =  "Hello, World"

    10.         g = t

    11. }

    12.  

    13. Func main ()  {

    14.   &NBSP ;     go setup ()

    15.         for g == nil  {

    16.         

    17.         print ( g.msg)

    18. }

Even if g! = Nil is detected in main and then exits the loop, do not assume that you can see that the value of g.msg is initialized. The workarounds for the above examples are consistent: Please use synchronization explicitly !

Something

To figure it out, the concurrent design features of Go are mainly two counter-conventional:

    • Concurrent Goroutine between the default is not mutual acquaintance, even can not think of the other side exists, o__o "... ;
    • The sequential experience may be different between concurrent snippets, that is, go allows the code order to be adjusted in the language hierarchy, (it says that the relationship between snippets has "before", "after", and "concurrency" three, not "before" or "after")

Go concurrency is a bit weird, think about it, why is it like this? Because it is a common optimization measure to adjust order on program execution, the go language is a more natural "implementation" language, but it is not a more natural "programming" language, because when coding, the me often defaults everything from top to bottom, even if multithreading is the case. So write goroutine, should be more out of the code to synchronize.

Reprinted from: http://studygolang.com/articles/1597

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.