package main
import "log"
import "runtime"
var a string
var done bool
func setup() {
a = "hello, world"
done = true
if done {
log.Println(len(a)) // always 12 once printed
}
}
func main() {
go setup()
for !done {
runtime.Gosched()
}
log.Println(a) // expected to print: hello, world
}
hello, world
text will be printed. However, the behavior of this program is compiler and CPU dependent. If the program is compiled with a different compiler, or with a later compiler version, or it runs on a different architecture, the hello, world
text might not be printed, or a text different from hello, world
might be printed. The reason is compilers and CPUs may exchange the execution orders of the first two lines in the setup
function, so the final effect of the setup
function may become to
func setup() {
done = true
a = "hello, world"
if done {
log.Println(len(a))
}
}
setup
goroutine in the above program is unable to observe the reordering, so the log.Println(len(a))
line will always print 12
(if this line gets executed before the program exits). However, the main goroutine may observe the reordering, which is why the printed text might be not hello, world
.
a
and done
. So, the above program is a showcase full of concurrent programming mistakes. A professional Go programmer should not make these mistakes.
go build -race
command provided in Go Toolchain to build a program, then we can run the outputted executable to check whether or not there are data races in the program.
A
is guaranteed to happen before event B
, it means any of the goroutines involved in the two events will observe that any of the statements presented before event A
in source code will be executed before any of the statements presented after event B
in source code. For other irrelevant goroutines, the observed orders may be different from the just described.
x, y = 123, 789
will be executed before the call fmt.Println(x)
, and the call fmt.Println(x)
will be executed before the call fmt.Println(y)
.
var x, y int
func f1() {
x, y = 123, 789
go func() {
fmt.Println(x)
go func() {
fmt.Println(y)
}()
}()
}
var x, y int
func f2() {
go func() {
// Might print 0, 123, or some others.
fmt.Println(x)
}()
go func() {
// Might print 0, 789, or some others.
fmt.Println(y)
}()
x, y = 123, 789
}
m == 0
), the nth successful receive from that channel happens before the nth successful send on that channel completes.
func f3() {
var a, b int
var c = make(chan bool)
go func() {
a = 1
c <- true
if b != 1 { // impossible
panic("b != 1") // will never happen
}
}()
go func() {
b = 1
<-c
if a != 1 { // impossible
panic("a != 1") // will never happen
}
}()
}
b = 1
absolutely ends before the evaluation of the condition b != 1
.
a = 1
absolutely ends before the evaluation of the condition a != 1
.
panic
in the above example will never get executed. However, the panic
calls in the following example may get executed.
func f4() {
var a, b, x, y int
c := make(chan bool)
go func() {
a = 1
c <- true
x = 1
}()
go func() {
b = 1
<-c
y = 1
}()
// Many data races are in this goroutine.
// Don't write code as such.
go func() {
if x == 1 {
if a != 1 { // possible
panic("a != 1") // may happen
}
if b != 1 { // possible
panic("b != 1") // may happen
}
}
if y == 1 {
if a != 1 { // possible
panic("a != 1") // may happen
}
if b != 1 { // possible
panic("b != 1") // may happen
}
}
}()
}
c
. It will not be guaranteed to observe the orders observed by the first two new created goroutines. So, any of the four panic
calls may get executed.
panic
calls in the above example will never get executed, however, the Go official documentation never makes such guarantees. So the code in the above example is not cross-compiler or cross-compiler-version compatible. We should stick to the Go official documentation to write professional Go code.
func f5() {
var k, l, m, n, x, y int
c := make(chan bool, 2)
go func() {
k = 1
c <- true
l = 1
c <- true
m = 1
c <- true
n = 1
}()
go func() {
x = 1
<-c
y = 1
}()
}
k = 1
ends before the execution of y = 1
.
x = 1
ends before the execution of n = 1
.
x = 1
is not guaranteed to happen before the execution of l = 1
and m = 1
, and the execution of l = 1
and m = 1
is not guaranteed to happen before the execution of y = 1
.
k = 1
is guaranteed to end before the execution of y = 1
, but not guaranteed to end before the execution of x = 1
,
func f6() {
var k, x, y int
c := make(chan bool, 1)
go func() {
c <- true
k = 1
close(c)
}()
go func() {
<-c
x = 1
<-c
y = 1
}()
}
m
of type Mutex
or RWMutex
in the sync
standard package, the nth successful m.Unlock()
method call happens before the (n+1)th m.Lock()
method call returns.
rw
of type RWMutex
, if its nth rw.Lock()
method call has returned, then its successful nth rw.Unlock()
method call happens before the return of any rw.RLock()
method call which is guaranteed to happen after the nth rw.Lock()
method call returns.
rw
of type RWMutex
, if its nth rw.RLock()
method call has returned, then its mth successful rw.RUnlock()
method call, where m <= n
, happens before the return of any rw.Lock()
method call which is guaranteed to happen after the nth rw.RLock()
method call returns.
a = 1
ends before the execution of b = 1
.
m = 1
ends before the execution of n = 1
.
x = 1
ends before the execution of y = 1
.
func fab() {
var a, b int
var l sync.Mutex // or sync.RWMutex
l.Lock()
go func() {
l.Lock()
b = 1
l.Unlock()
}()
go func() {
a = 1
l.Unlock()
}()
}
func fmn() {
var m, n int
var l sync.RWMutex
l.RLock()
go func() {
l.Lock()
n = 1
l.Unlock()
}()
go func() {
m = 1
l.RUnlock()
}()
}
func fxy() {
var x, y int
var l sync.RWMutex
l.Lock()
go func() {
l.RLock()
y = 1
l.RUnlock()
}()
go func() {
x = 1
l.Unlock()
}()
}
p = 1
is not guaranteed to end before the execution of q = 1
, though most compilers do make such guarantees.
var p, q int
func fpq() {
var l sync.Mutex
p = 1
l.Lock()
l.Unlock()
q = 1
}
sync.WaitGroup
values
sync.WaitGroup
value wg
is not zero. If there is a group of wg.Add(n)
method calls invoked after the given time, and we can make sure that only the last returned call among the group of calls will modify the counter maintained by wg
to zero, then each of the group of calls is guaranteed to happen before the return of a wg.Wait
method call which is invoked after the given time.
wg.Done()
is equivalent to wg.Add(-1)
.
sync.WaitGroup
type to get how to use sync.WaitGroup
values.
sync.Once
values
sync.Once
type to get the order guarantees made by sync.Once
values and how to use sync.Once
values.
sync.Cond
values
sync.Cond
values. Please read the explanations for the sync.Cond
type to get how to use sync.Cond
values.
b
is guaranteed to happen before the atomic read operation with result 1
on the same variable. Consequently, the write operation on the variable a
is also guaranteed to happen before the read operation on the same variable. So the following program is guaranteed to print 2
.
package main
import (
"fmt"
"runtime"
"sync/atomic"
)
func main() {
var a, b int32 = 0, 0
go func() {
a = 2
atomic.StoreInt32(&b, 1)
}()
for {
if n := atomic.LoadInt32(&b); n == 1 {
// The following line always prints 2.
fmt.Println(a)
break
}
runtime.Gosched()
}
}
runtime.SetFinalizer(x, f)
happens before the finalization call f(x)
.
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.