Safe Resource Patterns
Resource Management Strategies
Preventing Race Conditions
Race conditions occur when multiple goroutines access shared resources concurrently, potentially causing unpredictable behavior.
graph TD
A[Goroutine 1] -->|Unsafe Access| B[Shared Resource]
C[Goroutine 2] -->|Concurrent Access| B
Safe Access Patterns
Pattern |
Mechanism |
Use Case |
Mutex |
Exclusive Locking |
Protecting shared data structures |
Channels |
Message Passing |
Coordinating goroutine communication |
Atomic Operations |
Lock-free Synchronization |
Simple numeric operations |
Atomic Operations Example
package main
import (
"fmt"
"sync/atomic"
"time"
)
type SafeCounter struct {
value int64
}
func (c *SafeCounter) Increment() {
atomic.AddInt64(&c.value, 1)
}
func (c *SafeCounter) Value() int64 {
return atomic.LoadInt64(&c.value)
}
func main() {
counter := &SafeCounter{}
for i := 0; i < 1000; i++ {
go counter.Increment()
}
time.Sleep(time.Second)
fmt.Println("Final Value:", counter.Value())
}
Context for Cancellation and Timeout
The context
package provides a powerful way to manage goroutine lifecycles and propagate cancellation.
package main
import (
"context"
"fmt"
"time"
)
func longRunningTask(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Task cancelled")
return
default:
fmt.Println("Working...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go longRunningTask(ctx)
time.Sleep(3 * time.Second)
}
Synchronization Primitives
WaitGroup for Coordinating Goroutines
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers completed")
}
Resource Pool Pattern
type ResourcePool struct {
resources chan Resource
maxSize int
}
func NewResourcePool(maxSize int) *ResourcePool {
pool := &ResourcePool{
resources: make(chan Resource, maxSize),
maxSize: maxSize,
}
for i := 0; i < maxSize; i++ {
pool.resources <- createResource()
}
return pool
}
func (p *ResourcePool) Acquire() Resource {
return <-p.resources
}
func (p *ResourcePool) Release(r Resource) {
p.resources <- r
}
Best Practices
- Minimize shared state
- Prefer channels over mutexes
- Use context for cancellation
- Implement proper resource cleanup
Common Anti-Patterns
- Global shared state
- Excessive locking
- Goroutine leaks
- Improper error handling
LabEx Learning Recommendation
LabEx provides comprehensive environments to practice and master these safe resource management techniques in Golang.
graph TD
A[Resource Management] --> B[Mutex]
A --> C[Atomic Operations]
A --> D[Channel-based Synchronization]
B --> E[High Overhead]
C --> F[Low Overhead]
D --> G[Moderate Overhead]