在 Go 语言中,除了使用 sync.Mutex
锁来保护共享变量外,还有以下几种方式可以实现安全读写共享变量:
1. 使用 sync.RWMutex
(读写锁)
- 原理:
sync.RWMutex
是一种读写锁,允许多个读操作同时进行(读锁),但写操作是互斥的(写锁)。- 读多写少场景:适合读操作远多于写操作的场景,能显著提高并发性能。
- 特点:
- 读锁(
RLock
/RUnlock
)允许多个协程同时读取共享变量。 - 写锁(
Lock
/Unlock
)确保同一时间只有一个协程可以写入共享变量。
- 读锁(
- 示例代码:
var ( rwMutex sync.RWMutex sharedVar int ) func read() { rwMutex.RLock() defer rwMutex.RUnlock() fmt.Println("Read:", sharedVar) } func write(value int) { rwMutex.Lock() defer rwMutex.Unlock() sharedVar = value fmt.Println("Write:", sharedVar) }
2. 使用 sync/atomic
(原子操作)
- 原理:
sync/atomic
包提供了对基本类型(如int32
、int64
、uint32
等)的原子操作,确保操作不可分割。 - 特点:
- 高效:比锁更轻量,适合简单的计数器或标志位。
- 局限性:仅适用于基本数据类型,无法处理复杂结构。
- 示例代码:
var counter int64 func increment(wg *sync.WaitGroup) { defer wg.Done() atomic.AddInt64(&counter, 1) // 原子加法 } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Final Counter:", counter) }
3. 使用 Channel
(通道)
- 原理:
通过Channel
在协程之间传递数据,将共享变量的读写操作限制在同一个协程中,避免直接共享变量。 - 特点:
- 通信代替共享:通过
Channel
传递数据,而不是直接共享变量。 - 线程安全:
Channel
的操作是线程安全的。 - 阻塞机制:无缓冲
Channel
的发送和接收是同步的。
- 通信代替共享:通过
- 示例代码:
func main() { ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i // 发送数据到 Channel fmt.Println("Sent:", i) } close(ch) }() for value := range ch { fmt.Println("Received:", value) } }
4. 使用 sync.Map
(并发安全的 Map)
- 原理:
sync.Map
是 Go 1.9 引入的并发安全Map
,适用于需要频繁读写的键值对场景。 - 特点:
- 线程安全:支持并发读写操作。
- 性能优化:内部使用分段锁(Sharding)减少锁竞争。
- 示例代码:
var m sync.Map func main() { // 存储键值对 m.Store("key1", "value1") // 读取值 if value, ok := m.Load("key1"); ok { fmt.Println("Loaded:", value) } // 删除键值对 m.Delete("key1") }
5. 使用 sync.Cond
(条件变量)
- 原理:
sync.Cond
配合sync.Mutex
使用,用于实现更复杂的同步逻辑(如生产者-消费者模型)。 - 特点:
- 条件等待:协程可以在条件未满足时等待,直到被通知。
- 适用场景:需要根据特定条件控制协程执行顺序的场景。
- 示例代码:
var ( cond *sync.Cond shared int ) func main() { cond = sync.NewCond(&sync.Mutex{}) go func() { cond.L.Lock() fmt.Println("Goroutine 1 waiting...") cond.Wait() fmt.Println("Goroutine 1 proceeding") cond.L.Unlock() }() cond.L.Lock() fmt.Println("Signaling all goroutines") cond.Broadcast() // 广播通知所有等待的协程 cond.L.Unlock() }
总结
方法 | 适用场景 | 特点 |
---|---|---|
sync.RWMutex | 读多写少的场景 | 提高并发性能 |
sync/atomic | 简单数据类型的计数器或标志位 | 高效,但仅限基本类型 |
Channel | 协程间通信和数据传递 | 通过通信代替共享,线程安全 |
sync.Map | 并发键值对存储 | 内置并发安全,适合频繁读写 |
sync.Cond | 复杂条件同步(如生产者-消费者) | 需配合 sync.Mutex 使用 |
根据具体需求选择合适的方法,例如:
- 简单计数器:使用
sync/atomic
。 - 键值对存储:使用
sync.Map
。 - 生产者-消费者模型:优先选择
Channel
。