性能特点分析
- 读多写少场景
- 原子操作:原子操作非常轻量级,对于简单的数值读写操作,在这种场景下性能极佳。由于原子操作不会阻塞其他goroutine,多个读操作可以并行执行,不会因为锁竞争产生额外开销。例如
atomic.LoadInt64
和atomic.StoreInt64
等函数,在多goroutine频繁读取一个共享变量值时,能高效执行。
- 互斥锁:互斥锁在每次读操作时也需要获取锁,虽然读操作本身不改变数据,但获取和释放锁的过程仍有一定开销。当读操作非常频繁时,锁的竞争会导致性能下降,因为每次只能有一个goroutine获取到锁进行读操作,其他goroutine需要等待。
- 写多读少场景
- 原子操作:对于简单的写操作,原子操作依然高效。但如果写操作涉及复杂的数据结构或逻辑,原子操作可能就不太适用,因为原子操作通常针对简单数据类型(如整数、指针等)的单一操作。对于复杂写操作,可能需要多次原子操作才能完成,这可能导致数据不一致问题。
- 互斥锁:互斥锁能确保写操作的原子性和数据一致性,即使是复杂的写逻辑也能保证并发安全。在写多读少场景下,虽然写操作会阻塞读操作,但由于写操作本身频率不高,整体性能影响相对较小。与原子操作相比,互斥锁能更好地处理复杂数据结构的写操作。
选择依据
- 数据类型和操作复杂度
- 如果是简单数据类型(如
int
、int64
、uintptr
等)且操作只是简单的读写,原子操作是首选,因其轻量级且高效。
- 若涉及复杂数据结构(如自定义结构体、链表等)或复杂的读写逻辑,互斥锁更合适,它能提供更全面的同步控制。
- 并发读写频率
- 读多写少场景优先考虑原子操作,减少锁竞争开销;写多读少场景下,互斥锁能更好地保证数据一致性,即使有一定锁竞争也能接受。
示例代码
- 原子操作示例(读多写少场景)
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var counter int64
func main() {
var wg sync.WaitGroup
numRoutines := 1000
for i := 0; i < numRoutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}()
}
wg.Wait()
fmt.Println("Final counter value:", atomic.LoadInt64(&counter))
}
- 互斥锁示例(写多读少场景)
package main
import (
"fmt"
"sync"
)
type Data struct {
value int
mu sync.Mutex
}
func (d *Data) update(newValue int) {
d.mu.Lock()
d.value = newValue
d.mu.Unlock()
}
func (d *Data) read() int {
d.mu.Lock()
v := d.value
d.mu.Unlock()
return v
}
func main() {
data := Data{}
var wg sync.WaitGroup
// 模拟写操作
wg.Add(1)
go func() {
defer wg.Done()
data.update(42)
}()
// 模拟读操作
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read value:", data.read())
}()
}
wg.Wait()
}