Go’s concurrency model is one of its most powerful features. This guide explores goroutines, channels, and advanced patterns for writing efficient concurrent code.
Goroutines Basics
Goroutines are lightweight threads managed by the Go runtime:
package main
import (
"fmt"
"time"
)
func main() {
// Start goroutine
go sayHello("world")
// Anonymous goroutine
go func() {
fmt.Println("Anonymous function")
}()
time.Sleep(time.Second)
}
func sayHello(s string) {
fmt.Println("Hello", s)
}Channels
Channels enable communication between goroutines:
func main() {
messages := make(chan string)
// Send in goroutine
go func() {
messages <- "ping"
}()
// Receive
msg := <-messages
fmt.Println(msg)
}Buffered Channels
// Buffered channel (capacity 2)
ch := make(chan int, 2)
ch <- 1
ch <- 2
// ch <- 3 // Would block
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2Worker Pool Pattern
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// Start workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= 9; a++ {
<-results
}
}Fan-Out, Fan-In
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
channels[i] = worker(input)
}
return channels
}
func fanIn(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for n := range c {
out <- n
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}Select Statement
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("Received", msg1)
case msg2 := <-c2:
fmt.Println("Received", msg2)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
}
}Context for Cancellation
func operation(ctx context.Context) error {
select {
case <-time.After(5 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := operation(ctx); err != nil {
fmt.Println("Operation cancelled:", err)
}
}Pipeline Pattern
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Pipeline
for n := range square(generator(1, 2, 3, 4)) {
fmt.Println(n)
}
}Conclusion
Go’s concurrency primitives make it easy to write efficient parallel code. Master these patterns for building scalable applications.