Skip to content

Concurrency: The Go Way

Most languages bolt concurrency on top of shared-memory threads. Go was designed from the ground up around a different idea: Communicating Sequential Processes (CSP), formalized by Tony Hoare in 1978. The Go proverb that captures it best is:

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

In practice this means:

  • Goroutines are independently executing functions. They are cheap — a few kilobytes of stack that grows on demand — so you can have hundreds of thousands running concurrently.
  • Channels are typed conduits. Goroutines send values into a channel and receive values from another, transferring both data and synchronization in one operation.
  • The scheduler multiplexes goroutines onto a pool of OS threads (GOMAXPROCS threads by default, one per CPU core). You never manage threads directly.

This design makes concurrent programs easier to reason about. When data flows through channels, ownership is clear: the sender had it, now the receiver has it. No mutex needed for that transfer.

LessonTopic
This pageModel overview and first runnable example
Goroutinesgo f(), the scheduler, sync.WaitGroup
ChannelsCreate, send, receive, buffered vs unbuffered, close, range
SelectMultiplexing channels, default, timeouts
Sync primitivessync.Mutex, sync.Once, sync/atomic
Contextcontext.Context, cancellation, propagation
PatternsWorker pool, fan-out/fan-in, pipeline

The snippet below launches three goroutines in parallel. Each goroutine writes its result into a pre-allocated slice indexed by its own id, so output is always in order. sync.WaitGroup blocks main until all three are finished.

package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
results := make([]string, 3)
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
results[id] = fmt.Sprintf("goroutine %d says hello", id)
}(i)
}
wg.Wait()
for _, r := range results {
fmt.Println(r)
}
fmt.Println("main exits")
}
What does the Go proverb 'share memory by communicating' mean in practice?
What is the default number of OS threads Go uses to run goroutines?
Why is `wg.Add(1)` called BEFORE `go func()`?