Intermediate · Concurrency: goroutines, channels and context · topic 27 of 46 · 40 min
Channels: passing data between goroutines
Explanation
Why channels?
Goroutines often need to hand results or work to each other. Sharing a variable needs locks and care. Channels give a safer way: one goroutine sends a value, another receives it, and the hand-over itself is synchronised. Go's motto is: "Do not communicate by sharing memory; instead, share memory by communicating."
Making, sending and receiving
ch := make(chan string) // unbuffered channel of strings
go func() {
ch <- "order 101 ready" // send: arrow points INTO the channel
}()
msg := <-ch // receive: arrow points OUT of the channel
fmt.Println(msg) // order 101 readyUnbuffered versus buffered
| Unbuffered make(chan T) | Buffered make(chan T, 3) | |
|---|---|---|
| Send blocks when | No receiver is ready | The buffer is full |
| Receive blocks when | No sender is ready | The buffer is empty |
| Gives you | A guaranteed hand-over (both sides meet) | Some slack between a fast sender and a slower receiver |
jobs := make(chan int, 2)
jobs <- 1 // does not block: buffer has room
jobs <- 2 // does not block: buffer now full
// jobs <- 3 // would block until someone receives
fmt.Println(len(jobs), cap(jobs)) // 2 2Closing and ranging
close(ch) tells receivers that no more values will come. A for range loop over a channel receives values until the channel is closed and empty, then ends. The two-value receive v, ok := <-ch gives ok == false once the channel is closed and drained.
func produce(n int, out chan<- int) {
for i := 1; i <= n; i++ {
out <- i * i
}
close(out) // the SENDER closes
}
func main() {
squares := make(chan int)
go produce(4, squares)
for v := range squares { // stops when squares is closed
fmt.Print(v, " ")
}
// 1 4 9 16
}Rules that cause panics and hangs
| Action | Result |
|---|---|
| Send on a closed channel | panic: send on closed channel |
| Close an already closed channel | panic: close of closed channel |
| Receive from a closed channel | Returns the zero value immediately (ok is false) |
| Send or receive on a nil channel | Blocks forever |
| Every goroutine blocked on channels or locks | fatal error: all goroutines are asleep - deadlock! |
func main() {
ch := make(chan int)
ch <- 1 // nobody will ever receive
fmt.Println(<-ch)
}
// fatal error: all goroutines are asleep - deadlock!- Only the sender closes, and only when it is sure nothing else will send. Receivers never close.
- You do not have to close every channel. Close only when receivers need to know "no more values" (for example to end a range loop).
- With several senders, use a WaitGroup and close the channel once after all of them are done.
Directional channel types
chan<- T can only send; <-chan T can only receive. A normal chan T converts to either automatically when you pass it to a function. This documents who does what, and the compiler stops misuse.
A worker pool
func worker(id int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
results <- fmt.Sprintf("worker %d did job %d", id, j)
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan string, 10)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ { // 3 workers
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= 6; j++ { // 6 jobs
jobs <- j
}
close(jobs) // workers' range loops will end
go func() {
wg.Wait()
close(results) // close once, after all workers finish
}()
for r := range results {
fmt.Println(r)
}
}Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Channels: passing data between goroutines · 1/7
Channels let goroutines pass values safely. Share memory by communicating.
Prefer a video by a teacher? Find free videos on “golang channels buffered unbuffered close range worker pool” on YouTube ↗
In real life: Processing a day's orders with 4 workers
An e-commerce back office generates GST invoices for 50,000 orders every night. Starting 50,000 goroutines that all hit the database at once would overload it. Instead, a channel of order IDs feeds 4 workers, so at most 4 database queries run at a time, and a results channel collects totals for the report.
package main
import (
"fmt"
"sync"
)
func main() {
orders := make(chan int)
totals := make(chan int)
var wg sync.WaitGroup
for range 4 {
wg.Add(1)
go func() {
defer wg.Done()
for id := range orders {
totals <- id * 100 // pretend: build invoice, return amount
}
}()
}
go func() {
for id := 1; id <= 10; id++ {
orders <- id
}
close(orders)
}()
go func() {
wg.Wait()
close(totals)
}()
sum := 0
for t := range totals {
sum += t
}
fmt.Println("sum:", sum) // 5500
}Practice (0/4 done)
What happens? func main() { ch := make(chan int) ch <- 5 fmt.Println(<-ch) }
What does this print? ch := make(chan int, 1) ch <- 5 fmt.Println(<-ch)
What does this print? ch := make(chan int, 2) ch <- 1 close(ch) a, ok1 := <-ch b, ok2 := <-ch fmt.Println(a, ok1, b, ok2)
Build a pipeline of three stages connected by channels: generate the numbers 1 to 10, square each one in a second goroutine, and sum them in main. Each stage closes its output channel when done.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- select: waiting on several channelsConcurrency: goroutines, channels and context · same module
- Mutex, Once, atomic and the race detectorConcurrency: goroutines, channels and context · same module
- Concurrency interview questions and coding roundsCareers and interviews
- Message passing with channelsRust Programming · Concurrency and async
- Maps: key-value look-upsCollections and structs
- Interview questions: traits, errors, pointers and concurrencyRust Programming · Rust careers and interviews
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Who should close a channel?
Answer: The sender, when it will send no more
Only the sending side knows when there are no more values.
What is the type of a receive-only channel of ints?
Answer: <-chan int
<-chan int can only receive; chan<- int can only send.
What happens when you send on a closed channel?
Answer: panic: send on closed channel
Sending on a closed channel always panics.
What does a buffered channel of capacity 3 allow?
Answer: Up to 3 sends without a waiting receiver
Sends block only when the buffer is full.
What is the Go proverb about concurrency?
Answer: Share memory by communicating
Pass data through channels instead of sharing variables where you can.
A goroutine panics without recover. What happens?Related
Answer: The whole program crashes
An unrecovered panic in any goroutine terminates the process.
Why set a channel variable to nil inside a fan-in loop?Related
Answer: Because operations on a nil channel block forever, so that case is disabled
A nil channel case can never be selected.
How do several threads send on the same channel?Related
Answer: Clone the Sender for each thread
Sender can be cloned; each clone can move into a thread.
What happens to running goroutines when main returns?Related
Answer: They are stopped because the program exits
The program ends when main returns.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Concurrency: goroutines, channels and context” before its exam: select: waiting on several channels, Mutex, Once, atomic and the race detector, context: timeouts and cancellation.
- Module 6 exam: Concurrency · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Standard library essentials. 46 topics left in the course before the final exam.