跳转到主要内容
模块 7 · 第 4 课(共 5)课程中的34/50~14 min
模块课程(4/5)

`sync.Mutex` 和 `sync.WaitGroup`

通道是 Go 的主要习惯用法,但有时更传统 需要原语。 sync 包提供互斥体,等等 组和其他实用程序来协调共享状态。

sync.Mutex:排它锁

Go
import "sync"

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

一次只有 一个 goroutine 进入 Lock() 和 Unlock() 之间。 强制模式:defer mu.Unlock() 位于 Lock() 之后。

sync.RWMutex:多个读者,一名作者

对于读重状态:

Go
var mu sync.RWMutex

mu.RLock()              // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()

mu.Lock()               // write lock: esclusivo
data = newValue
mu.Unlock()

仅当读取确实压倒写入时才有用;否则是常规的 由于内部较低,sync.Mutex 更简单并且通常更快 争论。

sync.WaitGroup:等待 N 个 goroutine

Go
var wg sync.WaitGroup

for i := 0; i < 3; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        work(id)
    }(i)
}
wg.Wait()    // blocca finché tutti i Done() sono arrivati

工作流程:

  1. wg.Add(N) 将计数器增加 N(在启动 goroutine 之前)。
  2. 每个 goroutine 在完成时都会调用 wg.Done() (最好是通过 defer)。
  3. wg.Wait() 会阻塞,直到计数器返回到 0。

sync.Once:线程安全的延迟初始化

Go
var (
    once   sync.Once
    config *Config
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

即使在并发情况下,once.Do 中的回调也仅运行一次 来电。单例/惰性初始化模式。

互斥量与通道:何时使用什么

指南(也来自官方 Go FAQ):

案例首选工具
传递数据所有权频道
分配工作(工作队列)频道
协调独立的 goroutine频道
保护结构体字段互斥体
缓存/计数器互斥或原子
单例参考sync.Once

“编排通道,共享数据互斥”是一个很好的经验法则。

种族检测器

使用 -race 运行测试以发现数据争用:

Bash
go test -race ./...
go run -race main.go

竞争检测器检测二进制文件并记录每个不同步的 访问共享内存。它是验证并发代码的工具 #1 生产前。

尝试一下

锻炼#go.m7.l4.e1
尝试:0加载中...

使用 mu.Lock() + defer mu.Unlock() 保护计数的增量。

正在加载编辑器...
显示提示

在 Lock() 之后延迟 mu.Unlock() 保证即使在恐慌时也能释放。

3 次尝试后可用的解决方案

锻炼#go.m7.l4.e2
尝试:0加载中...

启动 3 个 goroutine 并使用sync.WaitGroup 等待所有它们(每个 goroutine 中的 Add(3)、Done()、Wait())。

正在加载编辑器...
显示提示

在启动 goroutine 之前添加;在 goroutine 内部使用 defer 完成。

3 次尝试后可用的解决方案

Quiz#go.m7.l4.e3
准备好

保证互斥体被释放的推荐模式是什么?

Go
mu.Lock()
// ?
答案选项

回顾

  • sync.Mutex 保护共享状态;始终 defer mu.Unlock()。
  • 使用指针接收器以避免复制互斥体。
  • sync.RWMutex 适用于多读者/单作者模式(仅当有帮助时)。
  • sync.WaitGroup:Add 在 go、Done 之前,并在 defer、Wait 等待。
  • sync.Once:线程安全的延迟初始化。 -“编排通道、共享数据互斥”。
  • go test -race:用于发现数据争用的工具#1。