sync.Mutex、sync.RWMutex与sync.Map的对比测试
在Go语言中,并发编程是核心特性之一,而处理并发访问共享资源时,锁(Locks)和并发安全的字典(Concurrent Maps)显得尤为重要。本文将通过对比测试,探讨Go标准库中的sync.Mutex、sync.RWMutex以及sync.Map在不同场景下的性能表现和使用场景。
一、sync.Mutex
sync.Mutex是Go标准库中最基本的互斥锁,它确保了同一时间只有一个goroutine可以访问被保护的资源。
示例代码
package main
import (
"fmt"
"sync"
)
func main() {
var mutex sync.Mutex
counter := 0
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
mutex.Lock()
counter++
fmt.Println("Counter:", counter)
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println("Program finished.")
}
性能分析
- 优点:实现简单,适用于写操作较多的场景。
- 缺点:在读多写少的场景下,由于读写操作都会阻塞其他goroutine,性能会有较大损失。
二、sync.RWMutex
sync.RWMutex是读写锁,允许多个goroutine同时读取共享资源,但写入时则阻塞所有其他goroutine(包括读取者)。
示例代码
package main
import (
"fmt"
"sync"
)
type SharedData struct {
value int
rwMu sync.RWMutex
}
func (s *SharedData) Read() int {
s.rwMu.RLock()
defer s.rwMu.RUnlock()
return s.value
}
func (s *SharedData) Write(newValue int) {
s.rwMu.Lock()
defer s.rwMu.Unlock()
s.value = newValue
}
func main() {
shared := &SharedData{value: 0}
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Printf("Reader: Value is %d\n", shared.Read())
}
}()
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
shared.Write(i)
fmt.Printf("Writer: Set value to %d\n", i)
}
}()
// Another reader goroutine
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Printf("Reader: Value is %d\n", shared.Read())
}
}()
wg.Wait()
}
性能分析
- 优点:在读多写少的场景下,性能显著提升,因为读取操作不会相互阻塞。
- 缺点:写操作会阻塞所有其他操作,包括读操作,因此在写操作频繁的场景下,性能优势不明显。
三、sync.Map
sync.Map是Go标准库中提供的一个并发安全的字典类型,它使用分段锁机制来实现高效的并发访问。
示例代码
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// Insert
m.Store("key1", "value1")
m.Store("key2", "value2")
// Load
value, ok := m.Load("key1")
if ok {
fmt.Println("Key: key1, Value:", value)
}
// Delete
m.Delete("key2")
// Range
m.Range(func(key, value interface{}) bool {
fmt.Printf("Key: %v, Value: %v\n", key, value)
return true // continue iteration
})
}
性能分析
- 优点:适用于高并发环境下的键值对存储和访问,内部实现优化减少了锁竞争。
- 缺点:相比于普通的map,其接口较为有限,且在某些场景下可能不如传统map加互斥锁的组合高效。
对比代码
benchmap.go
package mapbench
import "sync"
type MyMap struct {
sync.Mutex
m map[int]int
}
type MyRwMap struct {
sync.RWMutex
m map[int]int
}
var myMap *MyMap
var myRwMap *MyRwMap
var syncMap *sync.Map
func init() {
myMap = &MyMap{
m: make(map[int]int, 10000),
}
myRwMap = &MyRwMap{
m: make(map[int]int, 10000),
}
syncMap = &sync.Map{}
}
func builtinMapStore(k, v int) {
myMap.Lock()
defer myMap.Unlock()
myMap.m[k] = v
}
func builtinMapLookup(k int) int {
myMap.Lock()
defer myMap.Unlock()
if v, ok := myMap.m[k]; !ok {
return -1
} else {
return v
}
}
func builtinMapDelete(k int) {
myMap.Lock()
defer myMap.Unlock()
if _, ok := myMap.m[k]; !ok {
return
} else {
delete(myMap.m, k)
}
}
func builtinRwMapStore(k, v int) {
myRwMap.Lock()
defer myRwMap.Unlock()
myRwMap.m[k] = v
}
func builtinRwMapLookup(k int) int {
myRwMap.RLock()
defer myRwMap.RUnlock()
if v, ok := myRwMap.m[k]; !ok {
return -1
} else {
return v
}
}
func builtinRwMapDelete(k int) {
myRwMap.Lock()
defer myRwMap.Unlock()
if _, ok := myRwMap.m[k]; !ok {
return
} else {
delete(myRwMap.m, k)
}
}
func syncMapStore(k, v int) {
syncMap.Store(k, v)
}
func syncMapLookup(k int) int {
v, ok := syncMap.Load(k)
if !ok {
return -1
}
return v.(int)
}
func syncMapDelete(k int) {
syncMap.Delete(k)
}
benchmap_test.go
package mapbench2
import (
"math/rand"
"sync"
"testing"
"time"
)
func benchmark(b *testing.B, rw CommonRw, read, write int) {
b.RunParallel(func(pb *testing.PB) {
r := rand.New(rand.NewSource(time.Now().Unix()))
for pb.Next() {
// The loop body is executed b.N times total across all goroutines.
var wg sync.WaitGroup
for k := 0; k < read*1000; k++ {
ii := r.Intn(1e8)
wg.Add(1)
go func() {
rw.Read(ii)
wg.Done()
}()
}
for k := 0; k < write*1000; k++ {
jj := r.Intn(1e8)
wg.Add(1)
go func() {
rw.Store(jj, jj)
wg.Done()
}()
}
wg.Wait()
}
})
}
func BenchmarkReadMore(b *testing.B) {
benchmark(b, &MyMap{
m: make(map[int]int, 1000),
}, 9, 1)
}
func BenchmarkReadMoreRW(b *testing.B) {
benchmark(b, &MyRwMap{
m: make(map[int]int, 1000),
}, 9, 1)
}
func BenchmarkReadMoreSyncMap(b *testing.B) {
benchmark(b, &SyncMap{
m: sync.Map{},
}, 9, 1)
}
func BenchmarkWriteMore(b *testing.B) {
benchmark(b, &MyMap{
m: make(map[int]int, 1000),
}, 1, 9)
}
func BenchmarkWriteMoreRW(b *testing.B) {
benchmark(b, &MyRwMap{
m: make(map[int]int, 1000),
}, 1, 9)
}
func BenchmarkWriteMoreSyncMap(b *testing.B) {
benchmark(b, &SyncMap{
m: sync.Map{},
}, 1, 9)
}
func BenchmarkEqual(b *testing.B) {
benchmark(b, &MyMap{
m: make(map[int]int, 1000),
}, 5, 5)
}
func BenchmarkEqualRW(b *testing.B) {
benchmark(b, &MyRwMap{
m: make(map[int]int, 1000),
}, 5, 5)
}
func BenchmarkEqualSyncMap(b *testing.B) {
benchmark(b, &SyncMap{
m: sync.Map{},
}, 5, 5)
}
运行
go test -bench
输出
goos: darwin
goarch: arm64
pkg: demo/go-base/map/benchmark_for_map
BenchmarkBuiltinMapStoreParalell-8 7146411 198.3 ns/op 10 B/op 0 allocs/op
BenchmarkSyncMapStoreParalell-8 5471448 264.6 ns/op 43 B/op 3 allocs/op
BenchmarkBuiltinRwMapStoreParalell-8 7443933 198.9 ns/op 10 B/op 0 allocs/op
BenchmarkBuiltinMapLookupParalell-8 8305036 147.4 ns/op 0 B/op 0 allocs/op
BenchmarkBuiltinRwMapLookupParalell-8 8180395 144.0 ns/op 0 B/op 0 allocs/op
BenchmarkSyncMapLookupParalell-8 88137630 13.77 ns/op 0 B/op 0 allocs/op
BenchmarkBuiltinMapDeleteParalell-8 8230988 140.5 ns/op 0 B/op 0 allocs/op
BenchmarkBuiltinRwMapDeleteParalell-8 7505636 157.3 ns/op 0 B/op 0 allocs/op
BenchmarkSyncMapDeleteParalell-8 82433383 14.13 ns/op 0 B/op 0 allocs/op
PASS
ok demo/go-base/map/benchmark_for_map 16.196s
四、总结
- sync.Mutex:适用于写操作较多或读写比例均衡的场景,实现简单。
- sync.RWMutex:在读多写少的场景下性能优越,适合读取密集型应用。
- sync.Map:适用于高并发环境下的键值对存储,提供了较为简便的并发安全操作接口。
根据具体的应用场景选择合适的锁或并发安全字典,是提升Go程序性能的关键。 以上就是sync.Mutex,sync.RWMutex,sync.Map的对比。