sync包

在 Go 语言中,sync 包提供了基本的同步原语(synchronization primitives),用于控制并发执行的 goroutine 之间的执行顺序和共享资源的访问。它包含了一些常用的同步工具,如互斥锁(Mutex)、读写锁(RWMutex)、等待组(WaitGroup)、条件变量(Cond)等。

1. sync.Mutex(互斥锁)

sync.Mutex 是最基本的互斥锁,用于保护共享资源,防止多个 goroutine 同时访问导致数据竞争(data race)

基本用法

import "sync"

var (
    counter int
    mu      sync.Mutex
)

func increment() {
    mu.Lock()   // 加锁
    counter++   // 临界区代码
    mu.Unlock() // 解锁
}
  • Lock():获取锁,如果锁已被其他 goroutine 持有,则当前 goroutine 会阻塞,直到锁被释放。
  • Unlock():释放锁,允许其他 goroutine 获取锁。

注意事项

  • 必须成对使用Lock()Unlock() 必须配对,否则可能导致死锁或资源泄漏。
  • 避免重复解锁Unlock() 一个未加锁的 Mutex 会导致 panic。

2. sync.RWMutex(读写锁)

sync.RWMutex 是一种更高效的锁,适用于 读多写少 的场景。它允许多个 goroutine 同时读取数据,但写入时必须独占锁。

基本用法

var (
    data  map[string]string
    rwmu  sync.RWMutex
)

func readData(key string) string {
    rwmu.RLock()         // 读锁
    defer rwmu.RUnlock() // 释放读锁
    return data[key]
}

func writeData(key, value string) {
    rwmu.Lock()         // 写锁
    defer rwmu.Unlock() // 释放写锁
    data[key] = value
}
  • RLock() / RUnlock():读锁,允许多个 goroutine 同时读取。
  • Lock() / Unlock():写锁,同一时间只允许一个 goroutine 写入。

适用场景

  • 缓存系统:多个 goroutine 读取缓存,但写入缓存时需要独占锁。
  • 配置管理:多个 goroutine 读取配置,但更新配置时需要加写锁。

3. sync.WaitGroup(等待组)

sync.WaitGroup 用于等待一组 goroutine 完成执行,常用于并发任务的管理。

基本用法

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1) // 增加等待的 goroutine 数量
    go func(i int) {
        defer wg.Done() // 完成后减少计数
        fmt.Println(i)
    }(i)
}

wg.Wait() // 等待所有 goroutine 完成
  • Add(delta int):增加等待的 goroutine 数量(delta 可以是正数或负数)。
  • Done():相当于 Add(-1),表示一个 goroutine 完成。
  • Wait():阻塞当前 goroutine,直到所有 Add 的计数归零。

注意事项

  • Add 必须在 Wait 之前调用,否则可能导致 Wait 提前返回。
  • Done 必须调用,否则 Wait 会一直阻塞(可能导致死锁)。

4. sync.Once(单次执行)

sync.Once 确保某个操作 只执行一次,即使有多个 goroutine 调用。

基本用法

var once sync.Once

func initResource() {
    once.Do(func() {
        fmt.Println("Resource initialized (only once)")
    })
}

// 多个 goroutine 调用 initResource,但初始化代码只会执行一次
  • Do(f func()):传入的函数 f 保证只会执行一次,即使多个 goroutine 同时调用 Do

适用场景

  • 单例模式:确保全局变量只初始化一次。
  • 延迟初始化:某些资源(如数据库连接)只需要初始化一次。

5. sync.Cond(条件变量)

sync.Cond 用于在多个 goroutine 之间进行 条件等待和通知,通常与 MutexRWMutex 配合使用。

基本用法

var (
    mu    sync.Mutex
    cond  = sync.NewCond(&mu)
    ready bool
)

func worker() {
    mu.Lock()
    for !ready { // 等待条件满足
        cond.Wait() // 释放锁并阻塞,被唤醒后重新获取锁
    }
    fmt.Println("Worker: ready!")
    mu.Unlock()
}

func main() {
    go worker()

    time.Sleep(time.Second) // 模拟初始化
    mu.Lock()
    ready = true
    cond.Signal() // 唤醒一个等待的 goroutine
    // cond.Broadcast() // 唤醒所有等待的 goroutine
    mu.Unlock()
}
  • Wait():释放锁并阻塞,直到被 Signal()Broadcast() 唤醒。
  • Signal():唤醒 一个 等待的 goroutine。
  • Broadcast():唤醒 所有 等待的 goroutine。

适用场景

  • 生产者-消费者模型:当队列为空时,消费者等待;生产者生产数据后通知消费者。
  • 线程池任务调度:任务队列为空时,工作线程等待;有新任务时唤醒线程。

6. sync.Pool(对象池)

sync.Pool 用于缓存临时对象,减少内存分配和 GC 压力。

基本用法

var pool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 1024) // 创建新对象
    },
}

func main() {
    buf := pool.Get().([]byte) // 从池中获取对象
    defer pool.Put(buf)        // 使用完后放回池中

    // 使用 buf...
}
  • Get():从池中获取一个对象(如果池为空,则调用 New 创建新对象)。
  • Put(obj interface{}):将对象放回池中,供后续复用。

适用场景

  • 频繁创建和销毁的对象(如 []bytebytes.Buffer)。
  • 减少 GC 压力(避免频繁分配内存)。

注意事项

  • 池中的对象可能被 GC 回收sync.Pool 不保证对象长期存在,适合短期复用。
  • 不适合存储重要数据:因为对象可能被其他 goroutine 获取并修改。

总结

同步原语用途特点
sync.Mutex互斥锁保护共享资源,防止并发访问
sync.RWMutex读写锁读多写少场景,提高并发性能
sync.WaitGroup等待组等待多个 goroutine 完成
sync.Once单次执行确保操作只执行一次
sync.Cond条件变量协调 goroutine 间的条件等待
sync.Pool对象池缓存临时对象,减少内存分配

这些同步工具是 Go 并发编程的核心,合理使用可以避免数据竞争、死锁等问题,提高程序的性能和可靠性。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐