Go语言实战案例:使用sync.Mutex实现资源加锁

在并发编程中,数据共享是一把双刃剑。如果多个协程对同一个资源进行读写而没有任何同步机制,就可能会出现"竞态条件"或"数据竞争"的问题。Go语言为我们提供了 sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个 goroutine 能访问共享资源。

一、什么是 Mutex?

Mutex 是 mutual exclusion(互斥)的缩写,是 Go 标准库 sync 包提供的一种锁机制。它确保同一时间只有一个 goroutine 能进入临界区访问共享资源。

go 复制代码
type Mutex struct {
    // 内部实现省略
}

常用方法:

  • Lock():获取锁,如果锁已被占用,则阻塞等待;
  • Unlock():释放锁,其他阻塞的 goroutine 才能继续执行。

二、为什么需要加锁?

设想一个并发场景:多个 goroutine 同时对一个整数进行自增操作。虽然操作看似简单,但由于 i++ 并不是原子操作,它会被分解为三个步骤:

    1. 加载变量值;
    1. 执行加一;
    1. 保存回变量。

在这个过程中,如果没有互斥机制,多个 goroutine 很容易互相干扰,造成结果错误。

三、实战案例:并发安全的计数器

我们将构建一个并发安全的计数器,多个 goroutine 同时对它执行自增操作,确保最终计数正确。

1. 未加锁示例(存在竞态)

go 复制代码
package main

import (
    "fmt"
    "sync"
)

var counter int

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            counter++ // 非线程安全
            wg.Done()
        }()
    }

    wg.Wait()
    fmt.Println("最终计数器结果:", counter)
}

多次运行你会发现,结果每次都不一样,且通常小于1000。说明有些操作被"丢失"了。

2. 使用 sync.Mutex 加锁

go 复制代码
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            mu.Lock()
            counter++ // 临界区
            mu.Unlock()
            wg.Done()
        }()
    }

    wg.Wait()
    fmt.Println("最终计数器结果:", counter)
}

每次运行的结果都是 1000,说明加锁成功避免了竞态条件。

四、延伸:封装一个线程安全的计数器结构

为了更好地管理共享资源,我们可以将计数器封装成一个结构体,并内置锁机制。

go 复制代码
type SafeCounter struct {
    mu sync.Mutex
    val int
}

func (s *SafeCounter) Inc() {
    s.mu.Lock()
    s.val++
    s.mu.Unlock()
}

func (s *SafeCounter) Value() int {
    s.mu.Lock()
    defer s.mu.Unlock()
    return s.val
}

使用方式:

go 复制代码
func main() {
    var wg sync.WaitGroup
    counter := SafeCounter{}

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            counter.Inc()
            wg.Done()
        }()
    }

    wg.Wait()
    fmt.Println("最终计数器结果:", counter.Value())
}

五、sync.Mutex 使用建议

✅ 适用场景

  • • 多个 goroutine 对同一资源进行读写时;
  • • 逻辑简单,读写比例相近的场景。

⚠️ 注意事项

  • 死锁风险:锁住资源后若忘记释放会造成程序阻塞;
  • 性能瓶颈:锁会阻塞其他协程,过多使用可能降低并发效率;
  • 应尽量缩小锁的范围:只包裹必要的临界区。

六、与其他同步机制对比

同步方式 特点
sync.Mutex 最基础的锁,适用于简单同步控制
sync.RWMutex 读写锁,适用于读多写少的场景
channel 通过通信代替共享数据,较为优雅
sync.Atomic 操作 支持原子操作,性能比 mutex 高

七、结语

通过本案例我们深入理解了 Go 中 sync.Mutex 的使用方式和适用场景。虽然它使用起来非常简单,但如果忽略其潜在的陷阱,可能会导致难以发现的 bug 和性能问题。掌握并合理使用 Mutex 是每一位 Go 开发者进行并发编程的第一步。

相关推荐
candyTong3 小时前
Claude Code 的 Edit 工具是怎么工作的
javascript·后端·架构
GetcharZp4 小时前
GitHub 2.4 万 Star!D2 正在重新定义程序员画图方式
后端
审判长烧鸡5 小时前
【Go工具】go-playground是什么组织?官方的?
开发语言·安全·go
zhangxingchao5 小时前
多 Agent 架构到底怎么选?从 Claude Agent Teams、Cognition/Devin 到工程落地原则
前端·人工智能·后端
IT_陈寒6 小时前
SpringBoot那个自动配置的坑,害我排查到凌晨三点
前端·人工智能·后端
ServBay6 小时前
OpenCode 和它的7款必备插件
后端·github·ai编程
ping某6 小时前
逐字节拆解 tcpdump
后端
阿凡9807306 小时前
花 100 dollar,用 Claude 打通 EasyEDA&Fusion 双向同步
后端·程序员
irving同学462386 小时前
从零搭建生产级 RAG:Embedding、Chunking、Hybrid Search 与 Reranker
前端·后端
她的男孩6 小时前
从零搭一个企业后台,为什么我把能力拆成 Starter 和 Plugin
java·后端·架构