Advanced Go - Concurrency Patterns
Introduction to Concurrency in Go
Go is designed with concurrency in mind. It provides goroutines and channels to handle concurrent programming. Concurrency is not parallelism, but it enables the structuring of programs to handle multiple tasks at once. Let's explore some common concurrency patterns in Go.
1. Goroutines
A goroutine is a lightweight thread managed by the Go runtime. Goroutines are used to perform tasks concurrently. They are cheaper than threads in terms of memory and resources.
Example:
package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") }
hello world hello world hello world hello world hello world
2. Channels
Channels are used to send and receive data between goroutines. They provide a way for goroutines to communicate with each other and synchronize their execution.
Example:
package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c fmt.Println(x, y, x+y) }
17 -5 12
3. Buffered Channels
Buffered channels have a capacity and do not block until the buffer is full. They allow sending data to the channel without blocking the sender until the receiver is ready to receive.
Example:
package main import "fmt" func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 fmt.Println(<-ch) fmt.Println(<-ch) }
1 2
4. Select Statement
The select statement is used to wait on multiple channel operations. It blocks until one of its cases can proceed, then executes that case.
Example:
package main import "fmt" func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
0 1 1 2 3 5 8 13 21 34 quit
5. Worker Pools
Worker pools are a common concurrency pattern that allows you to limit the number of goroutines running at the same time, thus controlling the resource usage.
Example:
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { const numJobs = 5 jobs := make(chan int, numJobs) results := make(chan int, numJobs) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) for a := 1; a <= numJobs; a++ { <-results } }
worker 1 started job 1 worker 2 started job 2 worker 3 started job 3 worker 1 finished job 1 worker 1 started job 4 worker 2 finished job 2 worker 2 started job 5 worker 3 finished job 3 worker 1 finished job 4 worker 2 finished job 5