1. Common Causes of Goroutine Leaks
Here are the most frequent reasons why goroutines fail to exit gracefully, causing leaks:
1.1 Blocking on Channel Operations
- If a goroutine waits indefinitely on a channel that no one is writing to, it will never terminate.
Example:
1 2 3 4 5 6
| func leakExample() { ch := make(chan int) go func() { <-ch }() }
|
- Fix: Ensure the channel is closed or written to in all paths, or use timeouts with
select
.
1.2 Forgotten Goroutines in Background Tasks
- Goroutines spawned without any way to stop them may continue running indefinitely.
Example:
1 2 3 4 5 6 7 8
| func startLeakyTask() { go func() { for { time.Sleep(1 * time.Second) } }() }
|
- Fix: Use context cancellation to control when the goroutine should exit.
1.3 Deadlocks (Blocked on Mutexes or Channels)
- If two or more goroutines block waiting on each other (e.g., through channels or mutexes), they may never exit.
Example:
1 2 3 4 5 6 7 8 9 10
| var mu sync.Mutex
func deadlock() { mu.Lock() defer mu.Unlock() go func() { mu.Lock() defer mu.Unlock() }() }
|
- Fix: Ensure that locks and channels are always released properly to avoid deadlocks.
1.4 Missing Exit Conditions in select
Statements
- Goroutines using
select
without a proper exit condition may block forever.
Example:
1 2 3 4 5 6 7 8 9 10
| func leakInSelect() { ch := make(chan int) go func() { select { case val := <-ch: fmt.Println(val) } }() }
|
- Fix: Add timeouts or cancellation signals to ensure the goroutine exits when appropriate.
1.5 Waiting on Network or I/O Operations Indefinitely
- If a goroutine is waiting for a network call or I/O operation that never completes, it can leak.
Example:
1 2 3 4 5 6 7 8
| func leakyNetworkCall() { go func() { _, err := http.Get("http://example.com") if err != nil { fmt.Println("Request failed") } }() }
|
- Fix: Use timeouts for network or I/O operations.
2. How to Prevent Goroutine Leaks
2.1 Use Context Cancellation
Use context.Context
to control when a goroutine should exit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func startTaskWithContext(ctx context.Context) { go func() { for { select { case <-ctx.Done(): fmt.Println("Goroutine exiting") return default: time.Sleep(1 * time.Second) } } }() }
|
2.2 Ensure Channels Are Closed Properly
Always close channels to avoid blocking goroutines.
1 2 3 4 5 6 7 8 9
| ch := make(chan int)
go func() { for val := range ch { fmt.Println(val) } }()
close(ch)
|
2.3 Use sync.WaitGroup
to Manage Goroutines
Use a sync.WaitGroup
to ensure all goroutines complete before the program exits.
1 2 3 4 5 6 7 8 9 10 11 12
| var wg sync.WaitGroup
func task() { defer wg.Done() }
func main() { wg.Add(1) go task() wg.Wait() }
|
Reference
Code snippets