Concurrency lets your program do many things at the same time. Common patterns help you organize this work safely and clearly.
Common concurrency patterns in Go
package main import ( "fmt" "sync" ) func main() { // Example of a concurrency pattern }
Go uses goroutines to run functions concurrently.
Channels help goroutines communicate safely.
go func() {
fmt.Println("Hello from a goroutine")
}()ch := make(chan int) go func() { ch <- 42 // send value to channel }() val := <-ch // receive value from channel fmt.Println(val)
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() fmt.Println("Work done") }() wg.Wait()
jobs := make(chan int, 3) results := make(chan int, 3) // Worker function worker := func(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) results <- j * 2 } } func main() { for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { fmt.Println("result:", <-results) } }
This program uses a worker pool pattern. It starts 3 workers that get jobs from a channel. Each worker does some work (simulated by sleep) and sends results back. The main function waits for all workers to finish and then prints the results.
package main import ( "fmt" "sync" "time" ) func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { defer wg.Done() for j := range jobs { fmt.Printf("Worker %d started job %d\n", id, j) time.Sleep(time.Second) // simulate work fmt.Printf("Worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) var wg sync.WaitGroup // Start 3 workers for w := 1; w <= 3; w++ { wg.Add(1) go worker(w, jobs, results, &wg) } // Send 5 jobs for j := 1; j <= 5; j++ { jobs <- j } close(jobs) // Wait for all workers to finish wg.Wait() close(results) // Collect results for res := range results { fmt.Println("Result:", res) } }
Always close channels when no more values will be sent to avoid deadlocks.
Use WaitGroup to wait for goroutines to finish before exiting main.
Be careful to avoid race conditions when sharing data between goroutines.
Concurrency lets your program do many things at once using goroutines.
Channels help goroutines talk to each other safely.
Common patterns like worker pools and WaitGroups help organize concurrent work clearly.
Practice
WaitGroup in Go concurrency?Solution
Step 1: Understand WaitGroup's role
AWaitGroupis used to wait for multiple goroutines to complete their work.Step 2: Compare with other concurrency tools
Channels send data, semaphores limit concurrency, and goroutines run functions concurrently.Final Answer:
To wait for a collection of goroutines to finish before continuing -> Option CQuick Check:
WaitGroup = wait for goroutines [OK]
- Confusing WaitGroup with channels
- Thinking WaitGroup limits goroutines
- Using WaitGroup to send data
doWork()?Solution
Step 1: Recall goroutine syntax
In Go, the keywordgobefore a function call starts it as a goroutine.Step 2: Check options
Onlygo doWork()matches the correct syntax; others are invalid keywords.Final Answer:
go doWork() -> Option DQuick Check:
Start goroutine with 'go' keyword [OK]
- Using non-existent keywords like 'goroutine' or 'start'
- Omitting 'go' keyword
- Trying to run goroutine without parentheses
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
fmt.Println("Hello from goroutine 1")
wg.Done()
}()
go func() {
fmt.Println("Hello from goroutine 2")
wg.Done()
}()
wg.Wait()
fmt.Println("All goroutines finished")
}Solution
Step 1: Understand WaitGroup usage
The program adds 2 to the WaitGroup, then starts 2 goroutines that each print a message and callwg.Done().Step 2: Analyze output order
The main goroutine waits for both goroutines to finish before printing "All goroutines finished". The two goroutine prints can appear in any order but both appear before the final print.Final Answer:
Hello from goroutine 1 Hello from goroutine 2 All goroutines finished -> Option BQuick Check:
WaitGroup ensures final print after goroutines [OK]
- Assuming main prints before goroutines
- Ignoring WaitGroup synchronization
- Expecting goroutine prints after final print
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 10
}()
fmt.Println(<-ch)
fmt.Println(<-ch)
}Solution
Step 1: Analyze channel sends and receives
The goroutine sends one value (10) into the channel. The main goroutine reads twice from the channel.Step 2: Identify blocking behavior
The first read gets 10 successfully. The second read waits for another value that never comes, causing a deadlock (blocks forever).Final Answer:
Second read from channel blocks forever -> Option AQuick Check:
One send, two receives causes blocking [OK]
- Thinking channel must be closed before reading
- Assuming buffered channel fixes blocking here
- Believing goroutine not started properly
Solution
Step 1: Understand worker pool concurrency limit
To limit concurrent workers, a buffered channel (semaphore) of size 2 can control how many goroutines run simultaneously.Step 2: Use WaitGroup to wait for all jobs
A WaitGroup tracks all 5 jobs to ensure main waits until all finish before exiting.Final Answer:
Use a buffered channel of size 2 as a semaphore to limit goroutines, and a WaitGroup to wait for all jobs -> Option AQuick Check:
Semaphore + WaitGroup = controlled concurrency [OK]
- Starting all goroutines without limit
- Not using WaitGroup to wait for completion
- Using mutex incorrectly for concurrency limit
Ready to prove it?
Pass the test and this pattern moves unknown
