Advanced

Goroutines and Concurrency

Run tasks concurrently with lightweight goroutines and channels.

What you will see

6

About 2 min of reading

Try It

You will launch goroutines, share results with channels, wait for them with a WaitGroup, and drain a buffered channel with a range loop.

1

I start a goroutine that sends a result over a channel. The main routine blocks on the receive until the value arrives, then prints it.

package main import "fmt" func main() { done := make(chan string) go func() { done <- "concurrent work done" }() msg := <-done fmt.Println(msg) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
concurrent work done
A channel send blocks until someone receives, so the main routine waits here instead of racing ahead.
2

I launch a goroutine that sends a short string and receive it into a variable. The receive gets the exact value the goroutine sent.

package main import "fmt" func main() { ch := make(chan string) go func() { ch <- "done" }() msg := <-ch fmt.Println(msg) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
done
3

I pass a variadic list of numbers into a goroutine that returns their sum over a channel. The main routine prints the total it receives.

package main import "fmt" func sum(ch chan int, vals ...int) { total := 0 for _, v := range vals { total += v } ch <- total } func main() { ch := make(chan int) go sum(ch, 1, 2, 3, 4, 5) fmt.Println(<-ch) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
15
4

I launch several goroutines behind a WaitGroup. Each defers Done when it finishes, and Wait blocks until every one has reported back.

package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) go func() { defer wg.Done() }() } wg.Wait() fmt.Println("all done") }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
all done
Call wg.Add(1) before launching each goroutine, otherwise Wait may miss a goroutine and return too early.
5

I write three values into a buffered channel, close it, then range over the channel to add every value. Closing lets the loop end cleanly as it drains the buffer.

package main import "fmt" func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 close(ch) total := 0 for v := range ch { total += v } fmt.Println(total) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
6
Only close a channel once the sender is finished, and never both send and receive on the same channel.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Goroutines run in parallel; channels pass values safely between them.
1 Launch a goroutine with go
↓
2 It executes independently
↓
3 Send a value over a channel
↓
4 Another goroutine receives it
Why this works
The goroutine model

Goroutines are tiny (a few KB of stack) and multiplexed onto OS threads:

for i := 0; i < 1000; i++ {
    go doWork(i)
}

Thousands of goroutines run at once. The runtime decides how many threads back them, you write concurrency without managing threads.

Data races

Sharing memory without coordination causes races:

var count int
go func() { count++ }()
count++   // race!

Use sync/atomic or a sync.Mutex to protect shared state. Run with -race to catch problems in testing.

Examples

Starting a goroutine

Concurrent execution

func main() {
    go func() {
        fmt.Println("running concurrently")
    }()
    fmt.Println("main")
}

The go keyword launches a **goroutine*: a cheap thread managed by Go's runtime. Many can run at once.

Why it works: goroutines share memory but run independently; coordinate them with channels.

Channels

Passing data between goroutines

ch := make(chan string)
go func() { ch <- "done" }()
msg := <-ch   // receive
fmt.Println(msg)

A channel lets goroutines send and receive values. Sending blocks until someone receives, no shared memory needed.

Why it works: channels give safe communication without locks.

WaitGroups

Waiting for work

var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // do work
    }()
}
wg.Wait()   // blocks until all Done

sync.WaitGroup counts in-flight goroutines. Wait blocks until the count reaches zero.

Why it works: it coordinates many goroutines without manual timing.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Error Handling
Previous Lesson
Error Handling
Next Lesson
JSON and Structs
JSON and Structs →