Go has goroutines and channels in the language, and a memory model in a separate document, The Go Memory Model.
Goroutines
GoStmt = "go" Expression .
A go statement starts a function or method call as an independent concurrent
thread of control, a goroutine, in the same address space (spec: Go statements). The
expression must be a call and cannot be parenthesised. The same built-in functions are
restricted as for
expression statements.
The function value and arguments are evaluated in the calling goroutine, as for an ordinary
call, but the program does not wait for the call to finish. When the function ends, the
goroutine ends, and any return values are discarded. When main returns, the
program exits and other goroutines are not waited for.
go Server()
go func(ch chan<- bool) {
for {
sleep(10)
ch <- true
}
}(c)
Channels
A channel lets goroutines communicate by sending and receiving values of one element type. See Channel types for their syntax and direction rules. A channel may be used by any number of goroutines without further synchronisation. It acts as a first-in, first-out queue.
unbuffered := make(chan int)
buffered := make(chan int, 100)
buffered <- 1 // send
v := <-buffered // receive
v, ok := <-buffered // ok is false if the channel is closed and empty
close(buffered)
With capacity zero or absent, a channel is unbuffered: communication succeeds only when both sides are ready. Otherwise it is buffered: communication proceeds without blocking if the buffer is not full (sends) or not empty (receives).
| Operation | Nil channel | Closed channel | Open channel |
|---|---|---|---|
| Send | Blocks forever | Panics | Proceeds when a receiver is ready, or there is room in the buffer |
| Receive | Blocks forever | Yields buffered values, then the zero value without blocking | Blocks until a value is available |
| Close | Panics | Panics | Marks the channel closed |
len | 0 | Queued values | Queued values |
cap | 0 | Buffer size | Buffer size |
- The two-value receive,
x, ok := <-ch, givesok == falsewhen the value is a zero value produced because the channel is closed and empty. - A
closeon a receive-only channel is a compile-time error. - A
for … range chloop receives until the channel is closed; see Statements.
Select
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr = Expression .
A select statement chooses which of a set of send or receive operations
proceeds (spec: Select statements). It runs in these steps:
- For all the cases, the channel operands of receives, and the channel and value of sends, are evaluated exactly once, in source order, on entering. Side effects happen whichever case is chosen. Expressions on the left of a receive are not evaluated yet.
- If any communications can proceed, one of them is chosen by a uniform pseudo-random
selection. Otherwise a
defaultcase is chosen if there is one. If not, the statement blocks until a communication can proceed. - The chosen communication, unless it is the default, is performed.
- If it was a receive with an assignment or short declaration, the left-hand expressions are evaluated and the values assigned.
- The statement list of the chosen case runs.
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := <-c3:
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
default:
print("no communication\n")
}
select {} // block forever
A channel with a nil value can never proceed, so a select over only nil channels
with no default blocks forever, and a break leaves the
select, not an enclosing loop, unless labelled.
The memory model
The memory model says when a read of a variable in one goroutine is guaranteed to observe a
write in another. It was revised in Go 1.19 to match the approach of C, C++, Java, JavaScript,
Rust and Swift. Its advice is that programs that modify data accessed at the same time by
several goroutines must serialise that access, using channels or other primitives such as
sync and sync/atomic.
Data races
A data race is a write to a memory location happening concurrently with another read or write of that location, unless all the accesses involved are atomic. Programs free of data races behave as if all goroutines were multiplexed onto one processor, a property called DRF-SC (data-race-free programs execute in a sequentially consistent manner).
A race is still a bug, with limited effects. An implementation may report the race and stop
the program; go build -race does this. Otherwise each read of a location no
larger than a machine word must observe a value written to it and not yet overwritten.
Reads and writes of larger values may be split into machine-word operations, so races on
multi-word data such as interfaces, maps, slices and strings can lead to arbitrary memory
corruption.
Happens before
Within a goroutine, statements are sequenced before one another as the language requires. Synchronising operations add a synchronised before relation between goroutines. Happens before is the transitive closure of those two.
| Event | Synchronised before |
|---|---|
The go statement starting a goroutine | The start of the goroutine’s execution |
| A send on a channel | The completion of the corresponding receive |
| The closing of a channel | A receive that returns a zero value because the channel is closed |
| A receive from an unbuffered channel | The completion of the corresponding send |
| The kth receive on a channel with capacity C | The completion of the (k+C)th send |
Call n of l.Unlock() on a sync.Mutex or sync.RWMutex, for n < m | The return of call m of l.Lock() |
The completion of f() in once.Do(f) | The return of any call of once.Do(f) |
| An atomic operation whose effect is observed by another atomic operation | That other atomic operation |
SetFinalizer(x, f) | The finalisation call f(x) |
The completion of all init functions | The start of main.main |
- If package
pimportsq, the completion ofq’sinitfunctions happens before the start of any ofp’s. - The exit of a goroutine is not guaranteed to be synchronised before any event, so another goroutine may never see its writes without explicit synchronisation.
- All atomic operations in a program behave as if executed in one sequentially consistent
order, the same semantics as C++ sequentially consistent atomics and Java’s
volatilevariables. - The rule on buffered channels (Go 1.3 made it explicit) lets a buffered channel act as a counting semaphore.
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a) // guaranteed to print "hello, world"
}
c is synchronised before the receive completesvar limit = make(chan int, 3)
func main() {
for _, w := range work {
go func(w func()) {
limit <- 1
w()
<-limit
}(w)
}
select {}
}
w running at a time, from the capacity ruleOther synchronisation
The sync package also provides Cond, Map,
Pool, WaitGroup and Once, each documenting its own
guarantees. sync/atomic provides atomic values and operations, with atomic types
such as atomic.Int64 and atomic.Pointer[T] added in Go 1.19.