-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSyncronization techniques in go
156 lines (114 loc) · 4.84 KB
/
Syncronization techniques in go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
## Synchronization Techniques in Go: WaitGroups and Mutexes
While goroutines and channels handle communication and concurrency, Go provides additional synchronization primitives like `sync.WaitGroup` and `sync.Mutex` to manage shared resources and coordinate goroutines.
### WaitGroups
A `sync.WaitGroup` is used to wait for a collection of goroutines to finish. It provides three methods:
* `Add(delta int)`: Increments the counter by `delta`.
* `Done()`: Decrements the counter by 1.
* `Wait()`: Blocks until the counter is zero.
```go
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrement the counter when the goroutine finishes
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // Simulate some work
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Increment the counter for each goroutine
go worker(i, &wg) // Launch the goroutine
}
wg.Wait() // Wait for all goroutines to finish
fmt.Println("All workers done")
}
```
In this example, the `WaitGroup` ensures that the main function waits for all three worker goroutines to complete before printing "All workers done." The `defer wg.Done()` is crucial; it guarantees that the counter is decremented even if the worker function panics.
### Mutexes
A `sync.Mutex` (mutual exclusion) is used to protect shared resources from concurrent access. It provides two methods:
* `Lock()`: Locks the mutex. Only one goroutine can hold the lock at a time.
* `Unlock()`: Unlocks the mutex, allowing other goroutines to acquire the lock.
```go
package main
import (
"fmt"
"sync"
"time"
)
var counter int
var mutex sync.Mutex
func increment() {
mutex.Lock() // Acquire the lock
counter++
mutex.Unlock() // Release the lock
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}
```
Without the mutex, this code would have a race condition, and the final value of `counter` would be unpredictable. The mutex ensures that only one goroutine can access and modify `counter` at a time, preventing data corruption.
### RWMutex (Read/Write Mutex)
A `sync.RWMutex` provides more fine-grained control over shared resources by distinguishing between read and write operations. It provides the following methods:
* `RLock()`: Acquires a read lock. Multiple goroutines can hold a read lock simultaneously.
* `RUnlock()`: Releases a read lock.
* `Lock()`: Acquires a write lock. Only one goroutine can hold a write lock.
* `Unlock()`: Releases a write lock.
```go
package main
import (
"fmt"
"sync"
"time"
)
var data int
var rwMutex sync.RWMutex
func readData() {
rwMutex.RLock() // Acquire a read lock
fmt.Println("Reading data:", data)
time.Sleep(100 * time.Millisecond) // Simulate reading
rwMutex.RUnlock() // Release the read lock
}
func writeData(value int) {
rwMutex.Lock() // Acquire a write lock
fmt.Println("Writing data:", value)
data = value
time.Sleep(100 * time.Millisecond) // Simulate writing
rwMutex.Unlock() // Release the write lock
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ { //Multiple readers
wg.Add(1)
go func() {
defer wg.Done()
readData()
}()
}
wg.Add(1) //Single writer
go func() {
defer wg.Done()
writeData(100)
}()
wg.Wait()
}
```
`RWMutex` is useful when you have many read operations and relatively few write operations. It allows multiple readers to access the data concurrently, improving performance.
### Important Considerations:
* **Deadlocks:** Be careful to avoid deadlocks, which occur when two or more goroutines are blocked indefinitely, waiting for each other.
* **Race Conditions:** Ensure proper synchronization to prevent race conditions, which occur when multiple goroutines access and modify shared data concurrently, leading to unpredictable results.
* **Defer for Unlocking:** Always use `defer mutex.Unlock()` and `defer rwMutex.RUnlock()` after acquiring a lock to ensure that the lock is released even if the function panics. This prevents deadlocks.
By using `sync.WaitGroup`, `sync.Mutex`, and `sync.RWMutex` effectively, you can write robust and efficient concurrent programs in Go that safely manage shared resources and coordinate goroutines.