Go Goroutines
Lightweight concurrent functions in Go
⥠What are Goroutines?
Goroutines are lightweight threads managed by Go runtime. They enable concurrent execution with minimal memory overhead, allowing thousands to run simultaneously in a single program efficiently.
// Basic goroutine example
package main
import (
"fmt"
"time"
)
func main() {
go printNumbers()
go printLetters()
time.Sleep(3 * time.Second)
}
func printNumbers() {
for i := 1; i <= 5; i++ {
fmt.Printf("Number: %d\n", i)
time.Sleep(500 * time.Millisecond)
}
}
func printLetters() {
for i := 'A'; i <= 'E'; i++ {
fmt.Printf("Letter: %c\n", i)
time.Sleep(700 * time.Millisecond)
}
}
Goroutine Features
Lightweight
Only 2KB memory per goroutine
// Start 1000 goroutines easily
for i := 0; i < 1000; i++ {
go task(i)
}
Managed
Go runtime handles scheduling
// No manual thread management
go func() {
// Work happens here
}()
Scalable
Thousands can run simultaneously
// Multiplexed onto OS threads
runtime.GOMAXPROCS(
runtime.NumCPU()
)
Simple
Just add 'go' before function call
// Regular function call
doWork()
// Goroutine
go doWork()
ðđ Creating Goroutines
Start a goroutine by adding 'go' before any function call:
package main
import (
"fmt"
"time"
)
func main() {
// Regular function call - runs sequentially
sayHello("Alice")
sayHello("Bob")
// Goroutine calls - run concurrently
go sayHello("Charlie")
go sayHello("Diana")
// Anonymous goroutine
go func(name string) {
fmt.Printf("Hello from anonymous goroutine, %s!\n", name)
}("Eve")
// Wait for goroutines to complete
time.Sleep(2 * time.Second)
}
func sayHello(name string) {
for i := 0; i < 3; i++ {
fmt.Printf("Hello %s! (%d)\n", name, i+1)
time.Sleep(500 * time.Millisecond)
}
}
Output:
Hello Alice! (1) Hello Alice! (2) Hello Alice! (3) Hello Bob! (1) Hello Bob! (2) Hello Bob! (3) Hello Charlie! (1) Hello Diana! (1) Hello from anonymous goroutine, Eve! Hello Charlie! (2) Hello Diana! (2) Hello Charlie! (3) Hello Diana! (3)
ðđ WaitGroup for Synchronization
Use sync.WaitGroup to wait for goroutines to complete:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
tasks := []string{"Download", "Process", "Upload"}
for _, task := range tasks {
wg.Add(1) // Increment counter
go performTask(task, &wg)
}
wg.Wait() // Wait for all goroutines
fmt.Println("All tasks completed!")
}
func performTask(taskName string, wg *sync.WaitGroup) {
defer wg.Done() // Decrement counter when done
fmt.Printf("Starting %s...\n", taskName)
time.Sleep(time.Duration(len(taskName)) * 200 * time.Millisecond)
fmt.Printf("Completed %s!\n", taskName)
}
Output:
Starting Download... Starting Process... Starting Upload... Completed Upload! Completed Process! Completed Download! All tasks completed!
ðđ Goroutine Best Practices
Follow these practices for effective goroutine usage:
â Do:
- Use WaitGroup: Wait for goroutines to complete
- Handle errors: Don't ignore goroutine errors
- Limit goroutines: Don't create unlimited goroutines
- Use channels: For communication between goroutines
â Don't:
- Use time.Sleep: For synchronization in production
- Share memory: Without proper synchronization
- Ignore goroutine leaks: Always ensure goroutines can exit
// Good: Controlled goroutine creation
package main
import (
"fmt"
"sync"
)
func main() {
const maxGoroutines = 10
var wg sync.WaitGroup
semaphore := make(chan struct{}, maxGoroutines)
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
semaphore <- struct{}{} // Acquire
defer func() { <-semaphore }() // Release
// Do work here
fmt.Printf("Worker %d working\n", id)
}(i)
}
wg.Wait()
}