go快速上手:并发编程之sync.Mutex、sync.RWMutex与sync.Map对比

314 阅读4分钟

sync.Mutex、sync.RWMutex与sync.Map的对比测试

在Go语言中,并发编程是核心特性之一,而处理并发访问共享资源时,锁(Locks)和并发安全的字典(Concurrent Maps)显得尤为重要。本文将通过对比测试,探讨Go标准库中的sync.Mutexsync.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的对比。