深入理解Go语言的sync包 – 并发同步原语指南

深入理解Go语言的sync包 – 并发同步原语指南

1. sync包概述

Go语言的sync包提供了基本的同步原语,用于在并发程序中协调不同的goroutine。在多线程编程中,正确使用同步机制至关重要,可以避免数据竞争和确保程序行为的确定性。

2. 互斥锁 Mutex

2.1 基本用法

sync.Mutex是最简单的同步原语,保证同一时间只有一个goroutine能访问共享资源:

var (
    counter int
    mu      sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    counter++
    mu.Unlock()
}

2.2 使用建议

  • 锁定的区域应尽可能小
  • 确保所有可能的分支都会解锁
  • 使用defer mu.Unlock()可以避免忘记解锁
func safeWrite(m map[string]string, key, value string) {
    mu.Lock()
    defer mu.Unlock()
    m[key] = value
}

3. 读写锁 RWMutex

3.1 基本概念

sync.RWMutex允许多个读者或一个写者:

var (
    cache map[string]string
    rw    sync.RWMutex
)

func read(key string) string {
    rw.RLock()
    defer rw.RUnlock()
    return cache[key]
}

func write(key, value string) {
    rw.Lock()
    defer rw.Unlock()
    cache[key] = value
}

3.2 适用场景

  • 读多写少的场景
  • 数据读取频率远高于写入频率
  • 允许一定程度的一致性妥协

4. 等待组 WaitGroup

4.1 使用示例

sync.WaitGroup用于等待一组goroutine完成:

func process(data []string) {
    var wg sync.WaitGroup

    for _, item := range data {
        wg.Add(1)
        go func(item string) {
            defer wg.Done()
            // 处理item
        }(item)
    }

    wg.Wait() // 等待所有goroutine完成
}

4.2 最佳实践

  • Add()应在gouroutine外调用
  • 使用defer wg.Done()确保计数减少
  • 不要将WaitGroup传递给gouroutine,应传递指针

5. 一次性初始化 Once

5.1 原理与用法

sync.Once确保一个函数只执行一次:

var (
    config *Config
    once   sync.Once
)

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

5.2 使用场景

  • 懒加载
  • 单例模式实现
  • 全局初始化

6. 条件变量 Cond

6.1 基本使用

sync.Cond用于goroutine间的通知:

var (
    mu  sync.Mutex
    cv  = sync.NewCond(&mu)
    val int
)

func waitForValue() {
    mu.Lock()
    for val == 0 {
        cv.Wait() // 等待通知
    }
    fmt.Println("Got value:", val)
    mu.Unlock()
}

func setValue(v int) {
    mu.Lock()
    val = v
    mu.Unlock()
    cv.Broadcast() // 通知所有等待者
}

6.2 注意事项

  • 总是持有锁时调用Wait()
  • 检查条件应放在循环中
  • 使用Broadcast()Signal()唤醒等待者

7. 并发安全的Map

7.1 sync.Map特性

sync.Map是线程安全的map实现:

var m sync.Map

// 存储值
m.Store("key", "value")

// 加载值
if v, ok := m.Load("key"); ok {
    fmt.Println(v)
}

// 遍历所有键值
m.Range(func(k, v interface{}) bool {
    fmt.Println(k, v)
    return true // 继续遍历
})

7.2 适用场景

  • 读多写少
  • 键集合相对稳定
  • 每个键有独立的更新

8. 常见陷阱与最佳实践

8.1 避免死锁

  • 按固定顺序获取多个锁
  • 使用defer释放锁
  • 设置超时机制

8.2 性能考量

  • RWMutex不一定比Mutex快
  • sync.Map不是所有场景都比map+mutex更好
  • 监控锁竞争情况

8.3 错误处理

  • 使用TryLock(Go 1.18+)避免阻塞
  • 考虑上下文取消
  • 记录锁持有时间过长的场景

文末预告

下一期我们将深入探讨Go语言的context包,了解如何优雅地管理goroutine的生命周期,实现请求作用域的值传递、取消信号和超时控制等功能。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇