go 并发安全整型,map,数组如何操作

在 Go 中,为了在并发环境下安全地操作不同类型的数据结构,sync/atomic 提供了针对基本类型的原子操作,但对于像 map 和数组这样的复杂数据结构,没有直接的原子操作支持。我们需要根据具体场景选择合适的并发安全机制。以下分别讨论整型、map 和数组的并发安全实现方式。


1. 并发安全的整型:atomic.AddInt32

对于整型,sync/atomic 提供了直接的原子操作支持,非常适合处理简单的计数器或数值更新场景。例如:

  • atomic.AddInt32(&i, 1):原子递增 32 位整数。
  • atomic.AddInt64(&i, 1):原子递增 64 位整数。
  • 其他操作:atomic.LoadInt32(读取)、atomic.StoreInt32(写入)、atomic.CompareAndSwapInt32(比较并交换)等。

示例

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var i int32
    var wg sync.WaitGroup

    for j := 0; j < 100; j++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt32(&i, 1)
        }()
    }

    wg.Wait()
    fmt.Println(i) // 输出 100
}

2. 并发安全的 map

Go 的内置 map 类型不是并发安全的。如果多个 goroutine 同时读写同一个 map,会导致运行时错误(如 fatal error: concurrent map read and map write)。以下是几种实现并发安全 map 的方法:

方法 1:使用 sync.Mutex

用互斥锁保护 map 的读写操作。

package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    m  map[string]int
    mu sync.Mutex
}

func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.m[key] = value
}

func (sm *SafeMap) Get(key string) int {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    return sm.m[key]
}

func main() {
    sm := SafeMap{m: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            sm.Set(fmt.Sprintf("key%d", n), n)
        }(i)
    }

    wg.Wait()
    fmt.Println(sm.Get("key42")) // 输出 42
}

方法 2:使用 sync.RWMutex

如果 map 的读取操作远多于写入操作,可以使用读写锁 sync.RWMutex,允许多个 goroutine 同时读取,但写入时独占。

type SafeMap struct {
    m  map[string]int
    mu sync.RWMutex
}

func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.m[key] = value
}

func (sm *SafeMap) Get(key string) int {
    sm.mu.RLock()
    defer sm.mu.RUnlock()
    return sm.m[key]
}

方法 3:使用 sync.Map

Go 标准库提供了内置的并发安全 map 类型 sync.Map,专门为并发场景设计。它内部使用原子操作和读写分离优化,适合高并发读写。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var sm sync.Map
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            sm.Store(fmt.Sprintf("key%d", n), n)
        }(i)
    }

    wg.Wait()
    v, _ := sm.Load("key42")
    fmt.Println(v) // 输出 42
}
  • Store(key, value):写入。
  • Load(key):读取。
  • Delete(key):删除。

适用场景

  • sync.Map 适用于读多写少的场景,性能优于锁保护的普通 map
  • 如果需要复杂的逻辑或频繁写入,sync.Mutex 可能更灵活。

3. 并发安全的数组

Go 的数组(或切片,slice)也不是并发安全的。如果多个 goroutine 同时修改数组元素,会导致数据竞争。以下是几种解决方案:

方法 1:使用 sync.Mutex

用锁保护整个数组的读写。

package main

import (
    "fmt"
    "sync"
)

type SafeArray struct {
    arr []int
    mu  sync.Mutex
}

func (sa *SafeArray) Set(index, value int) {
    sa.mu.Lock()
    defer JwtUnlock()
    sa.arr[index] = value
}

func (sa *SafeArray) Get(index int) int {
    sa.mu.Lock()
    defer sa.mu.Unlock()
    return sa.arr[index]
}

func main() {
    sa := SafeArray{arr: make([]int, 10)}
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            sa.Set(n, n*2)
        }(i)
    }

    wg.Wait()
    fmt.Println(sa.Get(5)) // 输出 10
}

方法 2:每个元素独立加锁

如果数组很大且只需要并发修改不同元素,可以为每个元素分配独立的锁(例如用 sync.Mutex 切片)。这减少了锁竞争。

type SafeArray struct {
    arr    []int
    locks  []sync.Mutex
}

func (sa *SafeArray) Set(index, value int) {
    sa.locks[index].Lock()
    defer sa.locks[index].Unlock()
    sa.arr[index] = value
}

func (sa *SafeArray) Get(index int) int {
    sa.locks[index].Lock()
    defer sa.locks[index].Unlock()
    return sa.arr[index]
}

方法 3:无锁设计(特定场景)

如果数组的修改模式是“每个 goroutine 只操作自己的索引”,可以用无锁方式。例如,每个 goroutine 只更新自己的“槽”。

package main

import (
    "fmt"
    "sync"
)

func main() {
    arr := make([]int, 10)
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            arr[index] = index * 2 // 每个 goroutine 操作独立索引
        }(i)
    }

    wg.Wait()
    fmt.Println(arr[5]) // 输出 10
}

这种方法要求确保 goroutine 之间的操作不重叠,否则仍需加锁。


总结

  • 整型:使用 sync/atomic(如 atomic.AddInt32),高效且直接。
  • Map
    • sync.Mutexsync.RWMutex:灵活,适合复杂逻辑。
    • sync.Map:内置并发安全,适合高并发读写。
  • 数组
    • sync.Mutex:简单通用,保护整个数组。
    • 每个元素独立锁:减少竞争,适合大规模数组。
    • 无锁:仅限操作索引不重叠的场景。

根据具体需求(如性能、复杂度、并发模式),选择合适的工具是关键。

posted @ 2025-03-24 20:48  仁义礼智信的  阅读(111)  评论(0)    收藏  举报