Skip to content

在 Go 中,sync 包提供了基本的同步原件,用于在多个 goroutine 之间协调并发执行。sync 包是 Go 并发编程中非常重要的一部分,主要用于避免竞态条件、确保并发安全以及进行同步。

1. sync.Mutex(互斥锁)

Mutex 是最常用的同步原件,用于保证同一时刻只有一个 goroutine 可以访问共享资源。互斥锁通过 LockUnlock 方法来控制对临界区的访问。

1.1. 使用 sync.Mutex

go
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup

    // 启动 10 个 goroutine,每个 goroutine 增加 counter
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // 输出: Counter: 10
}
  • mu.Lock():加锁,进入临界区,其他 goroutine 无法再进入。
  • mu.Unlock():解锁,允许其他 goroutine 访问临界区。

Mutex 保证了在一个时刻,只有一个 goroutine 可以访问共享资源 counter,从而避免了竞态条件。

1.2. sync.MutexLockUnlock 关系

  • Lock 会阻塞当前 goroutine,直到锁可用。
  • Unlock 会解除阻塞,并允许其他 goroutine 获取锁。

注意:在使用 Mutex 时,务必确保在 Lock 后的代码执行完成后调用 Unlock,否则会导致死锁。通常使用 defer 来确保 Unlock 被执行。

2. sync.RWMutex(读写锁)

RWMutex 是一种改进的锁,支持多读单写的模式。多个 goroutine 可以同时读取共享资源,但写操作是独占的。在读操作较多、写操作较少的场景中,RWMutex 可以提高性能。

2.1. 使用 sync.RWMutex

go
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.RWMutex
)

func read() int {
    mu.RLock()         // 加读锁
    defer mu.RUnlock() // 解读锁
    return counter
}

func write(val int) {
    mu.Lock()         // 加写锁
    counter = val
    mu.Unlock()       // 解写锁
}

func main() {
    var wg sync.WaitGroup

    // 启动 10 个 goroutine 进行读操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(read())
        }()
    }

    // 启动 1 个 goroutine 进行写操作
    go func() {
        write(100)
    }()

    wg.Wait()
}
  • RLock:为读操作加锁,允许多个 goroutine 同时读取。
  • RUnlock:解读锁。
  • Lock:为写操作加锁,写操作时会阻止其他任何读操作和写操作。
  • Unlock:解写锁。

3. sync.WaitGroup(等待组)

WaitGroup 用于等待一组 goroutine 执行完毕。当多个 goroutine 并发运行时,WaitGroup 可以用于等待它们完成后再继续执行后续操作。常用于等待一组任务的完成。

3.1. 使用 sync.WaitGroup

go
package main

import (
    "fmt"
    "sync"
)

func doWork(i int, wg *sync.WaitGroup) {
    defer wg.Done() // 完成任务后调用 Done() 来减少计数
    fmt.Printf("Working on task %d\n", i)
}

func main() {
    var wg sync.WaitGroup

    // 启动 5 个 goroutine
    for i := 1; i <= 5; i++ {
        wg.Add(1) // 增加等待的 goroutine 数量
        go doWork(i, &wg)
    }

    // 等待所有的 goroutine 执行完毕
    wg.Wait()
    fmt.Println("All tasks completed")
}
  • wg.Add(n):增加等待的 goroutine 数量。
  • wg.Done():每个 goroutine 完成后调用 Done,减少等待计数。
  • wg.Wait():阻塞直到所有 goroutine 调用 Done,等待计数归零。

4. sync.Once(单次操作)

sync.Once 用于确保某些操作只执行一次。无论调用多少次,Once 的操作只会执行一次,通常用于初始化工作(例如初始化全局变量)。

4.1. 使用 sync.Once

go
package main

import (
    "fmt"
    "sync"
)

var once sync.Once

func initOnce() {
    fmt.Println("Initialization done")
}

func main() {
    // 即使调用多次 initOnce,初始化操作也只会执行一次
    once.Do(initOnce)
    once.Do(initOnce)
    once.Do(initOnce)
}
  • once.Do(fn):确保 fn 只执行一次,即使调用多次,fn 只会执行一次。

5. sync/atomic

sync/atomic 包提供了一些原子操作,可以在不使用锁的情况下保证对共享数据的安全操作。适用于计数器、标志位等需要频繁更新的场景,能够减少性能开销。

5.1. 原子操作示例

go
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

func main() {
    var wg sync.WaitGroup

    // 启动 10 个 goroutine 增加 counter
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // 输出: Counter: 10
}
  • atomic.AddInt64(&counter, 1):原子地将 counter 增加 1,确保并发安全。

6. sync.Pool(对象池)

sync.Pool 用于临时对象的存储池,它可以帮助你减少内存分配和垃圾回收的开销。适用于那些频繁创建和销毁的对象,能有效提高性能。

6.1. 使用 sync.Pool

go
package main

import (
    "fmt"
    "sync"
)

var pool = sync.Pool{
    New: func() interface{} {
        return "New object"
    },
}

func main() {
    // 从池中获取对象
    obj := pool.Get()
    fmt.Println(obj)

    // 将对象放回池中
    pool.Put("Reused object")

    // 再次获取对象,输出池中放回的对象
    obj2 := pool.Get()
    fmt.Println(obj2)
}
  • pool.Get():从池中获取一个对象。
  • pool.Put():将对象放回池中。

7. 总结

  • sync.Mutex:用于防止多个 goroutine 同时访问共享资源,通过加锁和解锁来实现同步。
  • sync.RWMutex:读写锁,允许多个 goroutine 同时读取资源,但写操作是独占的。
  • sync.WaitGroup:用于等待一组 goroutine 执行完毕,常用于并发任务的协调。
  • sync.Once:保证某个操作只执行一次,适用于初始化等场景。
  • sync/atomic:提供原子操作,减少锁的使用,提高性能,常用于计数器、标志位等。
  • sync.Pool:提供临时对象池,用于减少内存分配和垃圾回收的开销,适用于频繁创建和销毁的对象。

Go 的 sync 包为并发编程提供了丰富的工具,可以帮助我们更高效、更安全地进行并发操作。