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.Mutex或sync.RWMutex:灵活,适合复杂逻辑。sync.Map:内置并发安全,适合高并发读写。
- 数组:
sync.Mutex:简单通用,保护整个数组。- 每个元素独立锁:减少竞争,适合大规模数组。
- 无锁:仅限操作索引不重叠的场景。
根据具体需求(如性能、复杂度、并发模式),选择合适的工具是关键。

浙公网安备 33010602011771号