Introduction to Concurrency Patterns
1. What is Concurrency?
Concurrency is the ability of a program to make progress on multiple tasks simultaneously. In the context of Go programming, it is achieved using goroutines and channels. Concurrency enables efficient use of system resources and can significantly improve the performance of applications.
2. Goroutines
Goroutines are lightweight threads managed by the Go runtime. They are cheaper than traditional threads and allow you to run functions concurrently. You can start a goroutine by using the go
keyword followed by a function call.
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
func main() {
go printNumbers()
fmt.Println("Started goroutine")
time.Sleep(6 * time.Second)
fmt.Println("Main function ends")
}
3. Channels
Channels are a powerful concurrency primitive in Go that allow goroutines to communicate with each other and synchronize their execution. Channels are typed conduits through which you can send and receive values using the channel operator <-
.
package main
import (
"fmt"
)
func sum(a int, b int, result chan int) {
result <- a + b
}
func main() {
result := make(chan int)
go sum(3, 4, result)
fmt.Println("Sum is:", <-result)
}
4. Select Statement
The select
statement lets a goroutine wait on multiple communication operations. It blocks until one of its cases can proceed, then it executes that case. Use it to handle multiple channel operations.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "Message from channel 1"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "Message from channel 2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
5. Worker Pools
Worker pools are a concurrency pattern that allows you to limit the number of goroutines running concurrently and manage tasks efficiently. This pattern is useful when you have a large number of tasks to process, but want to limit the number of concurrent goroutines.
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("Worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("Worker", id, "finished job", 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
}
}
6. Conclusion
Concurrency patterns in Go provide powerful tools for building efficient and high-performing applications. By using goroutines, channels, the select statement, and worker pools, you can manage multiple tasks concurrently and make the best use of system resources. Understanding these patterns is essential for writing effective Go programs.