Introduction
Goroutines are a fundamental feature of Go, allowing you to run functions concurrently with other goroutines. They are lightweight, managed by the Go runtime, and are essential for building concurrent programs. In this chapter, you will learn the basics of creating and managing goroutines, along with best practices and common patterns.
Creating a Goroutine
A goroutine is created by prefixing a function call with the go
keyword. This function will run concurrently with the calling function.
Example: Basic Goroutine
Example:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello, World!")
}
func main() {
go sayHello() // Start a new goroutine
time.Sleep(1 * time.Second) // Give the goroutine time to run
}
In this example, the sayHello
function runs concurrently with the main
function.
Anonymous Goroutines
You can also start a goroutine using an anonymous function.
Example: Anonymous Goroutine
Example:
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("Hello from anonymous goroutine!")
}()
time.Sleep(1 * time.Second) // Give the goroutine time to run
}
In this example, an anonymous function is run concurrently with the main
function.
Synchronizing Goroutines
To coordinate the execution of goroutines, Go provides several synchronization primitives, such as channels and the sync
package.
Example: Using Channels
Channels allow goroutines to communicate with each other and synchronize their execution.
Example:
package main
import (
"fmt"
)
func sayHello(done chan bool) {
fmt.Println("Hello, World!")
done <- true // Signal that the function is done
}
func main() {
done := make(chan bool)
go sayHello(done)
<-done // Wait for the goroutine to finish
}
In this example, a channel is used to signal when the sayHello
function is done.
Example: Using WaitGroup
The sync.WaitGroup
is another way to wait for multiple goroutines to finish.
Example:
package main
import (
"fmt"
"sync"
)
func sayHello(wg *sync.WaitGroup) {
defer wg.Done() // Mark the goroutine as done
fmt.Println("Hello, World!")
}
func main() {
var wg sync.WaitGroup
wg.Add(1) // Add a goroutine to the wait group
go sayHello(&wg)
wg.Wait() // Wait for all goroutines to finish
}
In this example, a WaitGroup
is used to wait for the sayHello
function to finish.
Example: Spawning Multiple Goroutines
You can start multiple goroutines and synchronize their execution using channels or WaitGroup
.
Example: Multiple Goroutines with Channels
Example:
package main
import (
"fmt"
)
func worker(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() {
const numWorkers = 3
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
fmt.Println("Result:", <-results)
}
}
Example: Multiple Goroutines with WaitGroup
Example:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
// Simulate work
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
const numWorkers = 3
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}
Best Practices
- Avoid Global Variables: Minimize the use of global variables to reduce the risk of race conditions.
- Use Channels and Synchronization Primitives: Use channels and synchronization primitives like
sync.WaitGroup
andsync.Mutex
to coordinate goroutines. - Limit Goroutine Creation: Avoid creating too many goroutines, as this can lead to resource exhaustion.
- Handle Panics: Use
recover
within goroutines to handle panics and prevent unexpected crashes. - Monitor Goroutine Lifecycle: Be aware of the lifecycle of your goroutines to avoid leaks and ensure they complete as expected.
Conclusion
Goroutines are a powerful feature of Go that enable concurrent execution of functions. By understanding how to create, synchronize, and manage goroutines, you can write efficient and scalable concurrent programs. Proper use of channels and synchronization primitives is essential to coordinate the execution of goroutines and ensure that your programs run smoothly.