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

深入 Go 底层原理(八):sync 包的实现剖析

1. 引言

除了 channel,Go 还提供了传统的、基于共享内存的同步原语,它们都位于 sync 包中。在高并发场景下,正确且高效地使用这些工具至关重要。理解它们的底层实现,能帮助我们避免死锁、竞争条件,并写出性能更优的并发代码。

本文将深入 sync 包,重点剖析 MutexWaitGroupPool 这三个最常用工具的内部工作原理。

2. sync.Mutex (互斥锁)

Mutex 用于保护临界区,确保同一时间只有一个 goroutine 可以访问共享资源。

核心数据结构:

type Mutex struct {state int32  // 锁的状态 (锁定、唤醒、饥饿等)sema  uint32 // 信号量,用于唤醒等待的 goroutine
}
  • state: 这是一个复合状态字段,通过位操作(bit manipulation)来存储多个信息:

    • Locked Bit: 标记锁是否已被持有。

    • Woken Bit: 标记是否有 goroutine 已被唤醒。

    • Starving Bit: 标记锁是否进入“饥饿”模式。

    • Waiter Count: 记录等待锁的 goroutine 数量。

工作模式: Mutex 有两种工作模式:

  1. 正常模式 (Normal Mode):

    • 当一个 goroutine 请求锁时,如果锁未被持有,它会立即获得锁。

    • 如果锁已被持有,它会通过几次**自旋(Spinning)**尝试获取锁。自旋是指在不挂起 goroutine 的情况下,进行忙等待循环。这对于锁很快会被释放的场景能提升性能。

    • 如果自旋后仍未获取到锁,goroutine 会被加入等待队列并挂起。

    • 当锁被释放时,会唤醒等待队列中的一个 goroutine。被唤醒的 goroutine 和新来的 goroutine 会公平竞争。

  2. 饥饿模式 (Starving Mode):

    • 如果一个 goroutine 等待锁的时间超过了 1ms,Mutex 会切换到饥饿模式

    • 在饥饿模式下,锁的所有权会直接交接给等待队列中的队头 goroutine,新来的 goroutine 即使自旋也无法获取锁。

    • 目的:防止等待队列中的 goroutine 因为不断有新来的“插队者”而长时间“饿死”,保证了公平性。

3. sync.WaitGroup

WaitGroup 用于等待一组 goroutine 全部执行完毕。

核心数据结构:

type WaitGroup struct {noCopy noCopy // 保证 WaitGroup 不被复制state1 [3]uint32
}
  • state1: 同样是一个复合字段,存储了两个核心信息:

    • Counter: 一个 32 位的计数器,记录需要等待的 goroutine 数量。

    • Waiter Count: 等待 Wait() 的 goroutine 数量。

工作原理:

  • Add(delta int): 直接通过原子操作(atomic operations)给 Counter 增加 delta

  • Done(): 等价于 Add(-1),通过原子操作将 Counter 减一。当 Counter 变为 0 时,它会唤醒所有在 Wait() 处等待的 goroutine。

  • Wait():

    • 检查 Counter 是否为 0。如果是,立即返回。

    • 如果不为 0,它会通过信号量机制将当前 goroutine 挂起,直到 Counter 变为 0 时被 Done() 唤醒。

4. sync.Pool (临时对象池)

Pool 用于缓存和复用临时对象,以减轻 GC 压力。它特别适用于那些需要频繁创建和销毁、生命周期短暂的对象的场景。

核心设计:

  • Pool 是并发安全的

  • Pool 中的对象随时可能被 GC 回收。你不应该依赖 Pool 来存储有状态的、持久的对象。

  • 每个 P (Processor) 都有自己的本地池:为了避免锁竞争,sync.Pool 为每个 P 都维护了一个私有的对象列表(poolLocal)。

工作原理:

  • Get():

    1. 优先尝试从当前 P 的本地私有池 (private) 中获取对象。此操作无锁。

    2. 如果私有池为空,尝试从当前 P 的本地共享池 (shared) 中获取。此操作也无锁。

    3. 如果本地共享池也为空,尝试从其他 P 的共享池中窃取一个对象。

    4. 如果窃取也失败,最后会调用 Pool 中预设的 New 函数(如果存在)来创建一个新对象。

  • Put(x interface{}):

    • 将对象 x 放入当前 P 的本地私有池中。此操作无锁。

GC 与 Pool:

  • 在每次 GC 开始时,sync.Pool 会清理其缓存。它会移除所有 P 的本地共享池中的对象,并将它们移动到一个“受害者缓存”中。私有池中的对象则会被直接丢弃。这保证了 Pool 不会无限增长,也解释了为什么池中的对象是不稳定的。

http://www.xdnf.cn/news/16920.html

相关文章:

  • 动态规划经典模型:双数组问题的通用解决框架与实战
  • VirtualBox 的 HOST 键(主机键)是 右Ctrl 键(即键盘右侧的 Ctrl 键)笔记250802
  • 音视频学习(四十五):声音的产生
  • 图(遍历/最小生成树/单/多源最短路径)
  • Spring事务失效场景
  • Python 全局解释器锁
  • Web前端实现银河粒子流动特效的3种技术方案对比与实践
  • 使用C++实现日志(1)
  • 淘宝小程序的坑
  • 华为核心交换机S7700的内存OID
  • 阿里云:Ubuntu系统部署宝塔
  • JavaScript将String转为base64 笔记250802
  • Jotai:React轻量级原子化状态管理,告别重渲染困扰
  • Unity_数据持久化_XML基础
  • 计数组合学7.11(RSK算法)
  • 2024年网络安全预防
  • 仿muduo库实现高并发服务器
  • Python----大模型(基于LLaMA Factory角色扮演模型微调)
  • Kubernetes容器运行时-Docker or Containerd
  • 【最后一个单词的长度】
  • RK3399 启动流程 --从复位到系统加载
  • 双网卡UDP广播通信机制详解
  • Leetcode 11 java
  • fastGEO v1.7.0 大更新,支持PCA、差异分析、火山图、热图、差异箱线图、去批次等分析
  • uniapp 富文本rich-text 文本首行缩进和图片居中
  • Flutter开发 dart语言基本语法
  • 基于单片机一氧化碳CO检测/煤气防中毒检测报警系统
  • 深入理解 Docker 容器网络:为什么用 host 网络模式能解决连通性问题?
  • Vue3 setup的两个注意点
  • Spring AI MCP 技术深度解析:从工具集成到企业级实战