Shared Data Protection
Understanding Race Conditions
Race conditions occur when multiple goroutines access shared data concurrently, potentially leading to unpredictable and incorrect program behavior.
Potential Risks of Shared Data
graph TD
A[Concurrent Access] --> B[Data Inconsistency]
A --> C[Unexpected Modifications]
A --> D[Non-Deterministic Behavior]
Synchronization Mechanisms
1. Mutex (Mutual Exclusion)
Mutexes provide a way to ensure that only one goroutine can access a critical section at a time.
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
value int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func main() {
counter := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final counter value:", counter.value)
}
2. RWMutex (Read-Write Mutex)
Allows multiple readers or a single writer to access shared data.
package main
import (
"fmt"
"sync"
"time"
)
type SafeCache struct {
mu sync.RWMutex
data map[string]string
}
func (c *SafeCache) Read(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
value, exists := c.data[key]
return value, exists
}
func (c *SafeCache) Write(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
Synchronization Primitives Comparison
Primitive |
Use Case |
Characteristics |
Mutex |
Exclusive access |
Blocks all access during write |
RWMutex |
Multiple readers, single writer |
More flexible |
Atomic Operations |
Simple numeric operations |
Lowest overhead |
3. Atomic Operations
For simple numeric operations, atomic package provides lock-free synchronization.
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var counter int64 = 0
for i := 0; i < 1000; i++ {
go func() {
atomic.AddInt64(&counter, 1)
}()
}
time.Sleep(time.Second)
fmt.Println("Atomic counter:", counter)
}
Best Practices for Shared Data Protection
- Minimize shared state
- Use channels for communication
- Prefer immutable data
- Use appropriate synchronization mechanisms
Channel-Based Synchronization
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
<-results
}
}
Detecting Race Conditions
Go provides a race detector:
go run -race yourprogram.go
By understanding these shared data protection techniques, you can write safe and efficient concurrent programs using LabEx's recommended approaches.