性能差异
- atomic包:
- 原子操作通常非常轻量级,因为它们是由底层硬件指令支持的。例如在简单的计数器场景下,对一个整数类型进行原子加操作,不需要像锁那样涉及复杂的上下文切换等开销。
- 适用于非常简单的、对单个变量的读写操作,在高并发下性能优势明显,因为它不会像锁那样阻塞其他goroutine的运行,只要硬件支持原子操作,就能快速完成。
- sync.Mutex:
- 互斥锁相对来说开销较大。当一个goroutine获取锁时,如果锁已被占用,该goroutine会被阻塞并进入睡眠状态,直到锁被释放。这涉及到操作系统的调度等开销,在高并发场景下频繁的加锁解锁操作可能会导致性能瓶颈。
适用场景差异
- atomic包:
- 适用场景:主要用于对单个共享变量进行简单的、无逻辑依赖的读写操作。例如,在统计系统中对全局计数器的增减操作,或者在并发环境下对某个标志位的原子读写。
- 示例:实现一个简单的全局计数器。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var count int64
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
atomic.AddInt64(&count, 1)
}
}()
}
wg.Wait()
fmt.Println("Final count:", count)
}
- sync.Mutex:
- 适用场景:适用于对多个共享变量的读写操作,或者对共享资源进行复杂逻辑操作时,需要保证操作的原子性和一致性。例如,在实现一个并发安全的链表或者映射(map)时,因为涉及多个数据结构的读写和修改,使用互斥锁可以确保数据的一致性。
- 示例:实现一个并发安全的map。
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
data map[string]int
}
func (sm *SafeMap) Set(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.data == nil {
sm.data = make(map[string]int)
}
sm.data[key] = value
}
func (sm *SafeMap) Get(key string) (int, bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.data == nil {
return 0, false
}
value, exists := sm.data[key]
return value, exists
}
func main() {
sm := SafeMap{}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
sm.Set("key1", 100)
}()
go func() {
defer wg.Done()
value, exists := sm.Get("key1")
fmt.Println("Value:", value, "Exists:", exists)
}()
wg.Wait()
}
总结
- 优先选择atomic原语的情况:当只涉及对单个简单类型(如int、int64等)的共享变量进行简单的读写操作,并且需要极高的并发性能时,优先选择atomic包提供的原语。
- 优先选择sync.Mutex的情况:当涉及多个共享变量的读写操作,或者对共享资源的操作有复杂逻辑,需要保证数据一致性和操作原子性时,优先选择sync.Mutex。