- 使用
sync.Mutex
(互斥锁)
- 控制并发访问方式:通过
sync.Mutex
的Lock
和Unlock
方法来保护共享资源。在访问共享资源前调用Lock
方法,访问结束后调用Unlock
方法。如果有多个Goroutine同时尝试获取锁,只有一个Goroutine能获取到,其他Goroutine会阻塞,直到锁被释放。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- 优缺点:
- 优点:简单易用,适用于大多数需要保护共享资源的场景。对于读写操作比例没有特殊要求的场景都很适用。
- 缺点:性能问题,在高并发下,如果锁竞争激烈,会导致很多Goroutine阻塞,降低程序的并发性能。而且如果
Unlock
调用被遗漏,会导致死锁。
- 使用
sync.RWMutex
(读写互斥锁)
- 控制并发访问方式:适用于读多写少的场景。允许多个Goroutine同时读共享资源,但写操作时需要独占锁。读操作调用
RLock
,写操作调用Lock
,相应的结束后调用RUnlock
和Unlock
。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var (
data int
rwMutex sync.RWMutex
)
func read(wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.RLock()
fmt.Println("Read value:", data)
rwMutex.RUnlock()
}
func write(wg *sync.WaitGroup) {
defer wg.Done()
rwMutex.Lock()
data++
fmt.Println("Write value:", data)
rwMutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go read(&wg)
}
for i := 0; i < 2; i++ {
wg.Add(1)
go write(&wg)
}
wg.Wait()
}
- 优缺点:
- 优点:在读写操作比例不均衡,读操作远多于写操作的场景下,性能比
sync.Mutex
好,因为读操作可以并发进行,减少了等待时间。
- 缺点:相比
sync.Mutex
,实现更复杂。而且如果写操作频繁,读操作也会因为等待写锁的释放而阻塞,导致性能下降。
- 使用
sync.Cond
(条件变量)
- 控制并发访问方式:结合
sync.Mutex
使用,用于在某些条件满足时通知Goroutine。一个Goroutine可以等待某个条件变量被通知,其他Goroutine可以在满足条件时发送通知。
- 示例代码:
package main
import (
"fmt"
"sync"
"time"
)
var (
ready bool
mu sync.Mutex
cond sync.Cond
)
func worker(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
for!ready {
cond.Wait()
}
fmt.Println("Worker is working")
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go worker(&wg)
time.Sleep(2 * time.Second)
mu.Lock()
ready = true
cond.Broadcast()
mu.Unlock()
wg.Wait()
}
- 优缺点:
- 优点:适用于需要根据某些条件来决定是否进行下一步操作的场景,能够更灵活地控制Goroutine的执行顺序。
- 缺点:使用起来相对复杂,需要小心处理条件变量和锁的关系,否则容易出现死锁或竞态条件。
- 使用
sync.Map
- 控制并发访问方式:这是Go 1.9引入的并发安全的map。无需手动加锁,内部实现了并发控制机制,可以直接在多个Goroutine中安全地读写。
- 示例代码:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := fmt.Sprintf("key%d", n)
m.Store(key, n)
value, ok := m.Load(key)
if ok {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
}(i)
}
wg.Wait()
}
- 优缺点:
- 优点:使用简单,无需手动管理锁,适用于需要在并发环境下使用map的场景,性能在高并发下表现良好。
- 缺点:它不支持遍历(虽然可以通过
Range
方法进行类似遍历的操作,但与普通map的遍历行为有差异),也不支持获取map的长度。如果需要这些功能,可能不太适用。