当前位置:网站首页>[translation] go references - the go memory model | memory model for Chinese translation of official golang documents

[translation] go references - the go memory model | memory model for Chinese translation of official golang documents

2022-06-12 12:09:00 Just want to call Yoko

This article is published synchronously in : [ translate ] Go References - The Go Memory Model | golang Memory model for Chinese translation of official documents | yoko blog

Preface

This translation corresponds to the original
title :The Go Memory Model - Go References
author :Go Official documents
Address :https://golang.org/ref/mem

This document indicates yoko remarks The content of is my own notes , The rest are translations of the original English text .

Catalog

  • brief introduction
  • Suggest
  • Happens Before
  • Sync
    • init function
    • Create coroutines
    • Destroy the process
    • Use channel signal communication
    • lock
    • Once
  • Demonstrate the wrong use of synchronization primitives

brief introduction

golang The memory model of specifies the conditions on which you want to achieve this effect . What effect is achieved ? That is, modify a variable in a coroutine , When another coroutine reads this variable, it is necessary to ensure that the modified value is read .

Suggest

If the program modifies the data accessed by multiple processes at the same time , Then these access operations must be serialized .

To ensure serial access , have access to golang Of channel Operate or use sync and sync/atomic Synchronization primitives in the package to protect data .

If you need to read the rest of this document to understand the behavior of your program , So you're a little too smart .

forehead , Don't be too clever .

Happens Before

In a process , The actual order of execution of read and write operations must ensure that the behavior they represent is consistent with the order specified in the program . in other words , The compiler and the processor will only reorder the read and write operations within the collaboration without changing the program semantics . Because of this reordering , The execution sequence observed by one process may be different from that observed by another process . for instance , If a coroutine is executed a=1; b=2;, Another synergy that may be observed is b The update of variables occurs in a Before updating variables .

To illustrate the requirements for read and write operations , We defined happens before, A kind of golang The local execution sequence of a program's memory operations . If the event e1happens before event e2, So let's say e2happens aftere1. alike , If e1 No happens beforee2 also e1 either happens aftere2, So let's say e1 and e2happens concurrently.

yoko remarks :
happens before The defined execution order , It is a kind of confirmation 、 Unique execution sequence .
 That is, if I say a happens before b,
 that a It must have happened in b Previous , Not allowed a It happened in b After or a and b Simultaneous occurrences occur .
 let me put it another way , If there is a It happened in b After or a and b Simultaneous events , Then you can't say a happens before b.

not happens before, This needs special attention .
 If I say a not happens before b,
 So it could be a It happened in b after ; It could be a and b At the same time .
 As long as it's not a It happened in b Just before .

happens after Namely happens before Another way of saying that the subject and object of a statement change their positions , There's nothing to explain this .

In a single process ,happens before Sequence is the sequence described in the program .

The variable v Read operation r allow Observed for variable v Write operations for w(yoko remarks : there allow It means allow , It is possible to observe , It may not be observed ), The following conditions must be met at the same time :

  1. rnot happens beforew
  2. Nothing else happens afterw also happens beforer Write operations for

If you want to make sure that the variables v Read operation r To observe a specific pair of variables v Write operations for w, namely w yes r The only write operation allowed to be observed . Simply speaking , Make sure that r The observed w, The following conditions must be met at the same time :

  1. whappens beforer
  2. Other shared variables v Write operations for either happens beforew, or happens afterr

This set of conditions is more restrictive than the first set of conditions . It requires no other write operations and w/rhappens concurrently.

In a process , Because there is no concurrency , So the two definitions are the same : Read operations r The last write operation can be observed w. If a shared variable is accessed by multiple processes . Then we must use synchronous events to establish happens before Condition to ensure that the read operation observes the desired write operation .

The variable v Initialization of , The behavior is the same as doing a write operation in the memory model .

Read and write operations on variables that exceed a machine word length , Its behavior is the same as that of multiple single machine word size operations , Is an unspecified order .

Sync

init function

golang All in the program init function Running in the same process , But this collaboration may create other collaborations , These coroutines may run concurrently .

If p The package introduces q package , that q Of init The function will be fully executed before starting execution p Of init function.

The entry function of the program main.main In all init function Do it after all .

Create coroutines

Start a new process happens before The entrance to the implementation of this new process .

For example, the following program :

var a string

func f() {
	print(a)
}

func hello() {
	a = "hello, world"
	go f()
}

call hello Function will be printed at some point in the future "hello, world", This point in time may be hello After function execution .

Destroy the process

Process exit does not guarantee happens before Any event in the program . For example, the following program :

var a string

func hello() {
	go func() { a = "hello" }()
	print(a)
}

Assignment statements are not combined with any synchronous events , There is no guarantee that this assignment statement will be observed by any other coroutine . in fact , A radical compiler may remove the entire coroutine statement .

If the impact of one process needs to be observed by other processes , Lock or channel And other synchronization mechanisms to establish an association order .

Use channel signal communication

Use channel Communication is the main method of multiprocess synchronization . Each time to a specific channel Sending is associated with a slave channel Receive matching , Generally, the sending and receiving processes are different .

To the buffered channel send out happens before From the channel Finish receiving .(yoko remarks : namely channel The receiver of the will be blocked , Until other collaborations go channel Sent data )

This procedure :

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

func f() {
	a = "hello, world"
	c <- 0
}

func main() {
	go f()
	<-c
	print(a)
}

Will ensure printing "hello, world". Yes a Writing happens before Go to channel c send out , Go to channel c send out happens before from channel c Finish receiving , from channel c Finish receiving happens before Print .

Close a channel happens before from channel The receiver of returns 0 value .

In the previous example , take c <- 0 Replace with close(c), The program guarantees the same behavior .

No buffer channel receive happens before To this channel Completion of transmission .(yoko remarks : namely channel The sending place of will be blocked , Until other processes are executed from channel Reading data )

This procedure ( Compared with the above procedure , The send and receive statements are exchanged and no cache is used channel):

var c = make(chan int)
var a string

func f() {
	a = "hello, world"
	<-c
}
func main() {
	go f()
	c <- 0
	print(a)
}

Printing will also be guaranteed "hello, world". write in a Variable happens before from c receive , from c receive happens before Go to c Completion of transmission , Go to c Completion of transmission happens before Print .

If channel It's buffered ( such as ,c = make(chan int, 1)), Then the program is not guaranteed to print "hello, world".( You may print an empty string , A crash or something ).

The first k The secondary initialization space is C Of channel Reception happens before The first k+C Secondary channel Completion of transmission .

This rule outlines the previous one, which is also about buffering channel The rules of . It allows the use of buffered channel To count semaphores :channel The number of elements inside is equal to the number of semaphores actually used ,channel The size of the initialization space is equal to the maximum number of semaphores that can be used simultaneously , Go to channel Sending an element is equivalent to getting a semaphore , from channel Receiving an element is equivalent to releasing a semaphore . This is a common way to limit concurrency .

This program is work Each element of the list opens a coroutine , But these coroutines are used to limit the initialization size channel Ensure that there are at most three at the same time work In execution .

var limit = make(chan int, 3)

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

lock

sync The package implements two types of lock data ,sync.Mutex and sync.RWMutex.

For any sync.Mutex or sync.RWMutex Lock variables for l And two times n and m(n < m), Call No n Time l.Unlock()happens before Call No m Time l.Lock() Return .

This procedure :

var l sync.Mutex
var a string

func f() {
	a = "hello, world"
	l.Unlock()
}

func main() {
	l.Lock()
	go f()
	l.Lock()
	print(a)
}

Will ensure printing "hello, world". First call l.Unlock( stay f Function )happens before Second call l.Lock()( stay main Function ) Return , The second time l.Lock() Return happens before Print .

about sync.RWMutex Variable l Any call to l.RLock,l.RLock Block until n Secondary call l.UnLock, also n Time l.RUnlockhappens before The first n+1 Secondary call l.Lock.

yoko remarks :
 The description of the read-write lock in the original English text is a bit convoluted , Personal understanding of read-write lock is :
 The prerequisite for entering the read lock is , At present, there can be 0~n Read locks have been entered , But no write lock has been entered 
 The prerequisite for entering the write lock is , Currently, no read lock has been entered , And currently no write lock has been entered 
 So the original sentence actually means , The read lock can only be accessed after the write lock is released , Write locks can only be accessed after all read locks are released 

Once

sync In bag Once Type provides a security mechanism for initializing in a multi - process environment . Multithreading can be a specific function f perform once.Do(f), But there is only one f() Will be performed , And other calls will block until f() completion of enforcement .

Multi process use once.Do(f), Only the one that was executed f() Execute and return , Then the others return .

This procedure :

var a string
var once sync.Once

func setup() {
	a = "hello, world"
}

func doprint() {
	once.Do(setup)
	print(a)
}

func twoprint() {
	go doprint()
	go doprint()
}

call twoprint It will only call setup once .setup The function will call print Completed before . The result is "hello, world" It prints twice .

Demonstrate the wrong use of synchronization primitives

remember , Read operations r Concurrent write operations may be observed w Result . Even if this happens , It does not mean that r Subsequent readings will also observe the occurrence of r Previous w.

This procedure :

var a, b int

func f() {
	a = 1
	b = 2
}

func g() {
	print(b)
	print(a)
}

func main() {
	go f()
	g()
}

Possible g Function print first 2 Re print 0.

This fact makes some writing incorrect .

Double check lock is a means to try to avoid redundant synchronous operation . such as ,twoprint The program may be incorrectly rewritten to :

var a string
var done bool

func setup() {
	a = "hello, world"
	done = true
}

func doprint() {
	if !done {
		once.Do(setup)
	}
	print(a)
}

func twoprint() {
	go doprint()
	go doprint()
}

But that doesn't guarantee , stay doprint Function , Observed on done The writing of is equivalent to the observation of a Writing . This wrong version may incorrectly print out an empty string instead of "hello, world".

Another misnomer is busy waiting for a variable , It's like :

var a string
var done bool

func setup() {
	a = "hello, world"
	done = true
}

func main() {
	go setup()
	for !done {
	}
	print(a)
}

As I said before , There is no guarantee that main A pair of... Is observed in the function done The writing of means the observation of a Writing , All this program can also print out an empty string . What's worse is , There is no guarantee that done Will be written by main Function observed , Because there is no synchronized event between two threads .main A loop in a function is not guaranteed to end .

There are other scenes with some minor differences on the subject , Like this program .

type T struct {
	msg string
}

var g *T

func setup() {
	t := new(T)
	t.msg = "hello, world"
	g = t
}

func main() {
	go setup()
	for g == nil {
	}
	print(g.msg)
}

Even if main Function observed g != nil And exit the loop , There's no guarantee main Function can be observed for g.msg Modification of .

All these examples , The solution is the same : Using explicit synchronization primitives .

原网站

版权声明
本文为[Just want to call Yoko]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/163/202206121206218263.html