当前位置:首页 > Go > 正文

Go语言并发编程入门(sync.Mutex互斥锁实现线程安全详解)

Go语言 中,并发是其核心特性之一。通过 goroutine,我们可以轻松地启动成千上万个轻量级线程来执行任务。然而,当多个 goroutine 同时访问共享资源(如变量、切片、结构体等)时,就可能引发数据竞争(data race),导致程序行为不可预测甚至崩溃。

为了解决这个问题,Go 标准库提供了 sync.Mutex —— 一种互斥锁(Mutex),用于保证同一时间只有一个 goroutine 能访问临界区代码,从而实现线程安全

Go语言并发编程入门(sync.Mutex互斥锁实现线程安全详解) Go语言 sync.Mutex 互斥锁 线程安全 第1张

什么是 sync.Mutex?

sync.Mutex 是 Go 语言中用于保护共享资源不被多个 goroutine 同时修改的同步原语。它提供两个方法:

  • Lock():获取锁。如果锁已被其他 goroutine 持有,则当前 goroutine 会阻塞,直到锁被释放。
  • Unlock():释放锁,允许其他等待的 goroutine 获取该锁。

一个不安全的并发示例

我们先看一个没有使用互斥锁的反面例子:

package mainimport (    "fmt"    "sync")var counter intfunc main() {    var wg sync.WaitGroup    for i := 0; i < 1000; i++ {        wg.Add(1)        go func() {            defer wg.Done()            counter++ // 多个 goroutine 同时修改 counter,存在数据竞争!        }()    }    wg.Wait()    fmt.Println("Final counter:", counter) // 结果通常小于 1000}

运行这段代码,你会发现输出结果往往不是 1000,而是某个小于 1000 的数字。这是因为多个 goroutine 同时读取和写入 counter,导致某些自增操作被覆盖——这就是典型的数据竞争问题。

使用 sync.Mutex 实现线程安全

现在,我们引入 sync.Mutex 来保护对 counter 的访问:

package mainimport (    "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() {            defer wg.Done()            mu.Lock()   // 获取锁            counter++   // 安全地修改共享变量            mu.Unlock() // 释放锁        }()    }    wg.Wait()    fmt.Println("Final counter:", counter) // 输出一定是 1000}

这次,无论运行多少次,结果都是 1000!因为 mu.Lock()mu.Unlock() 之间的代码构成了一个临界区,确保同一时刻只有一个 goroutine 能执行 counter++

最佳实践与注意事项

  1. 及时释放锁:务必在获取锁后,在适当位置调用 Unlock()。推荐使用 defer mu.Unlock(),避免因 panic 或提前 return 导致死锁。
  2. 不要复制 Mutexsync.Mutex 是不可复制的类型。将其作为结构体字段时,应使用指针或确保结构体不被复制。
  3. 锁的粒度要合理:锁住的代码越少越好,以减少 goroutine 阻塞时间,提高并发性能。
  4. 避免死锁:不要在已持有锁的情况下再次调用 Lock()(除非使用 sync.RWMutex 或可重入锁,但 Go 默认 Mutex 不可重入)。

总结

Go语言 并发编程中,sync.Mutex 是实现线程安全最基础也最重要的工具之一。通过合理使用互斥锁,我们可以有效防止数据竞争,确保程序在高并发环境下的正确性。

记住:**共享内存 + 并发 = 需要同步机制**。而 sync.Mutex 正是你在 Go 中守护共享资源的第一道防线。

希望这篇教程能帮助你理解 sync.Mutex 互斥锁 的基本用法。动手试试吧!