Go语言同步原语与数据竞争:Mutex 与 RWMutex

在Go语言并发程序中,当多个 goroutine 同时读写同一共享变量时,如果不加以控制,会引发数据竞争(race condition),导致程序行为不可预期。

Go 提供了多种同步原语,最基本的就是 sync.Mutex 和 sync.RWMutex。


一、什么是数据竞争?

数据竞争发生在两个或多个 goroutine 同时访问相同的内存地址,并且至少有一个是写操作,且未使用同步机制。

表现包括:

  • • 输出错乱、值错误
  • • 程序崩溃或逻辑失常
  • • 非确定性 bug(最难排查)

二、sync.Mutex:互斥锁

sync.Mutex 是最常见的锁,用于保护临界区,使得同一时间只有一个 goroutine 可以进入。

示例:未加锁的并发写入(错误)

css 复制代码
var counter int

func increment() {
    for i := 0; i < 1000; i++ {
        counter++
    }
}

func main() {
    for i := 0; i < 10; i++ {
        go increment()
    }
    time.Sleep(time.Second)
    fmt.Println("Counter:", counter) // 不确定输出
}

正确使用 Mutex 加锁

arduino 复制代码
var (
    counter int
    mutex   sync.Mutex
)

func increment() {
    for i := 0; i < 1000; i++ {
        mutex.Lock()
        counter++
        mutex.Unlock()
    }
}

三、sync.RWMutex:读写互斥锁

相比 Mutex,sync.RWMutex 提供了更细粒度的控制:

操作 行为说明
Lock() 获取写锁,所有读写都被阻塞
RLock() 获取读锁,允许多个同时读
写锁优先级高 有写者等待时,新的读锁会被阻止

示例:多个读协程同时访问共享资源

go 复制代码
type SafeMap struct {
    data map[string]string
    mu   sync.RWMutex
}

func (s *SafeMap) Get(key string) string {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return s.data[key]
}

func (s *SafeMap) Set(key, val string) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.data[key] = val
}

四、使用场景对比

场景 推荐使用
多个读协程 + 偶尔写 RWMutex
频繁读写,互斥访问临界区 Mutex
极端并发 + 只读数据 无需加锁(只读)
精确锁粒度对性能有重要影响的系统 RWMutex 可调优

五、避免死锁的建议

  • • 加锁后一定记得解锁 ,推荐使用 defer
  • • 避免多个锁嵌套加锁(容易形成死锁环)
  • • 尽量缩小加锁范围,不要锁整个函数
  • • 避免在持锁状态下调用外部函数(可能阻塞)

六、使用 -race 检查数据竞争

Go 提供了内置的竞态检测工具:

go 复制代码
go run -race main.go

可自动分析是否存在数据竞争,是并发调试的利器。


七、小结

  • • sync.Mutex 是最基础的并发控制工具,适用于串行化访问。
  • • sync.RWMutex 适用于读多写少的场景,提升并发效率。
  • • 使用锁时一定要小心死锁与性能瓶颈,配合 -race 工具排查隐患。

相关推荐
我不会起名字3221 小时前
Redis 从 0 到 1(十九):JWT 还是 Redis Session?登录态存储的 Go 实现
redis·go·jwt·session·登录态
hourinX1 小时前
一张只有两种颜色的头像,为什么还要用 NRGBA?—— `gdenticon` 重构日志(四)
go
我不会起名字3221 小时前
Redis 从 0 到 1(十六):1 亿 UV 只占 12KB?计数去重三件套的 Go 实测
redis·go·uv·布隆过滤器·hyperloglog
FungLeo4 小时前
成为全栈·Go 后端篇·换后端不改前端:四个客户端如何做兼容回归
flutter·golang·go·成为全栈·兼容测试·taro 小程序
我不会起名字3225 小时前
Redis 从 0 到 1(二十一):P99 涨到 80ms,慢查询日志 5 步定位法 + Go 采集器
redis·go·慢查询·排查·slowlog
我不会起名字3225 小时前
Redis 从 0 到 1(十八):List、Pub/Sub、Stream 为什么生产只用 Stream?Go 实操
redis·消息队列·go·stream·pub/sub
孔令飞6 小时前
AI 岗位类别、要求、技能及学习路线
云原生·go·ai编程
君万1 天前
【LeetCode每日一题】回溯:46. 全排列 78. 子集 39. 组合总和
算法·leetcode·go
vipxieliang1 天前
Go 逃逸分析与内存优化:从读懂 -gcflags=“-m“ 到降低 GC 压力
go
小杨互联网2 天前
Go 现代 CLI 工具离线镜像合集
开发语言·后端·golang·开源·go·命令行