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 开发者进行并发编程的第一步。

相关推荐
我就是全世界15 分钟前
OpenCode Go 套餐划算吗:先看你用哪个模型
开发语言·后端·golang
乌暮1 小时前
请求进门先安检:Spring 拦截器与统一功能实战指南
java·开发语言·后端·学习·spring
学长毕设1 小时前
基于SpringBoot的烘焙商品商品系统-附源码
java·spring boot·后端
付威20232 小时前
Durable Execution 怎样设计:从运行记录到中断恢复
后端
LHICOD3 小时前
Linux-2
java·linux·后端·jar·vmware·svm·vm
小杨互联网3 小时前
Go 现代 CLI 工具离线镜像合集
开发语言·后端·golang·开源·go·命令行
lazy H3 小时前
【计算机网络】第 15 篇:Java BIO、NIO 与高并发网络通信——从 Socket 理解 I/O 模型
网络·后端·tcp/ip·计算机网络·nio·408
geovindu4 小时前
rust: Semaphore Pattern
开发语言·后端·设计模式·rust·代理模式·企业级信号量模式·同步型模式
Thneonl5 小时前
Claude Code 频繁 Compacting?发布次日我根治了 2 个痛点
后端·rust
hourinX6 小时前
从直接填格子到筛选图案:头像生成这部分是怎样改过来的—— gdenticon 重构日志(三)
go