Go语言底层(三): sync 锁 与 对象池
1. 背景
在并发编程中,正确地管理共享资源是构建高性能程序的关键。Go 语言标准库中的 sync 包提供了一组基础而强大的并发原语,用于实现安全的协程间同步与资源控制。本文将简要介绍 sync 包中常用的类型和方法: sync 锁 与 对象池,帮助开发者更高效地编写并发安全的 Go 程序。
2. 锁
go语言是出了名的高并发利器 , 但在高并发场景下 , 伴随而来的数据安全问题是需要解决的。 加锁就是其中的一个解决办法。
多个线程同时访问临界区,锁住一些共享资源, 以防止并发访问这些共享数据时可能导致的数据不一致问题。
获取锁的线程可以正常访问临界区,未获取到锁的线程等待锁释放后可以尝试获取锁。
sync.Locker 是 go 标准库 sync 下定义的锁接口:
// A Locker represents an object that can be locked and unlocked.
type Locker interface {Lock()Unlock()
}
任何实现了 Lock 和 Unlock 两个方法的类,都可以作为一种锁的实现。
Go 语言包中的 sync 包提供了两种锁类型:sync.Mutex 和 sync.RWMutex,前者是互斥锁,后者是读写锁。
2.1 互斥锁 (sync.Mutex)
互斥即不可同时运行。即使用了互斥锁的两个代码片段互相排斥,只有其中一个代码片段执行完成后,另一个才能执行。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
-
Lock 加锁
使用 Lock () 加锁后,该线程不能再继续对其加锁,否则会 panic。只有在 unlock () 之后才能再次 Lock ()。异步调用 Lock (),是正当的锁竞争,当然不会有 panic 了。适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁。 -
Unlock 释放锁
Unlock () 用于解锁 m,如果在使用 Unlock () 前未加锁,就会引起一个运行错误。已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁。
2.1.1 使用方法
var lck sync.Mutex
func foo() {lck.Lock() defer lck.Unlock()// ...
}
2.2 读写锁 (sync.RWMutex)
读写锁是分别针对读操作和写操作进行锁定和解锁操作的互斥锁。
RWMutex
提供四个方法:
func (*RWMutex) Lock // 写锁定
func (*RWMutex) Unlock // 写解锁
func (*RWMutex) RLock // 读锁定
func (*RWMutex) RUnlock // 读解锁
-
写锁定情况下,对读写锁进行读锁定或者写锁定,都将阻塞;而且读锁与写锁之间是互斥的;
-
读锁定情况下,对读写锁进行写锁定,将阻塞;加读锁时不会阻塞;
操作1 \ 操作2 | RLock() (读锁) | Lock() (写锁) | RUnlock() | Unlock() |
---|---|---|---|---|
RLock() | ✅ 并发允许 | ❌ 阻塞等待写锁释放 | ✅ 无影响 | ✅ 无影响 |
Lock() | ❌ 阻塞等待读锁释放 | ❌ 阻塞等待写锁释放 | ✅ 无影响 | ✅ 无影响 |
RUnlock() | ✅ 无影响 | ✅ 无影响 | ✅ 无影响 | ✅ 无影响 |
Unlock() | ✅ 无影响 | ✅ 无影响 | ✅ 无影响 | ✅ 无影响 |
读写锁的存在是为了解决读多写少时的性能问题,读场景较多时,读写锁可有效地减少锁阻塞的时间。
3. 对象池 (sync.Pool)
sync.Pool 的使用场景 : 保存和复用临时对象,减少内存分配,降低 GC 压力。
举例 : Gin
框架中的 context
包每次面对接口调用时都需要创建 ,贯穿整个调用链路。底层就使用对象池进行优化。
sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool
用于存储那些被分配了但是没有被使用,而未来可能会使用的值。
3.1 使用方法
只需要实现 New 函数即可。对象池中没有对象时,将会调用 New 函数创建。
初始化 :
var studentPool = sync.Pool{New: func() interface{} { return new(Student) },
}
关键操作 :
// Put adds x to the pool.
func (p *Pool) Put(x any);// Get selects an arbitrary item from the [Pool], removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to [Pool.Put] and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() any;
举例 :
stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)
- Get() 用于从对象池中获取对象,因为返回值是 interface{},因此需要类型转换。
- Put() 则是在对象使用完毕后,返回对象池。
3.2 底层解析
3.2.1 sync.Pool 数据结构
type Pool struct {noCopy noCopylocal unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocallocalSize uintptr // size of the local arrayvictim unsafe.Pointer // local from previous cyclevictimSize uintptr // size of victims array// New optionally specifies a function to generate// a value when Get would otherwise return nil.// It may not be changed concurrently with calls to Get.New func() }
• noCopy 防拷贝标志;
• local 类型为 [P]poolLocal
的数组,数组容量 P 为 goroutine 处理器 P 的个数;
• victim 为经过一轮 GC 回收,暂存的上一轮 local;类型于二级缓存 , 随时可能被GC 回收
• New 为用户指定的工厂函数,当 Pool 内存量元素不足时,会调用该函数构造新的元素.
[P]poolLocal 数组
暂时存储对象的对象池 , 每个 poolLocal
逻辑处理器分为 private
和 sharedList
两部分缓存数据
type poolLocal struct {poolLocalInternal
}// Local per-P Pool appendix.
type poolLocalInternal struct {private any // Can be used only by the respective P.shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
• poolLocal 为 Pool 中对应于某个 P 的缓存数据;
• poolLocalInternal.private:对应于某个 P 的私有元素,操作时无需加锁;
• poolLocalInternal.shared: 某个 P 下的共享元素链表,由于各 P 都有可能访问,因此需要加锁.
3.2.2 sync.Pool 的核心方法
3.2.2.1 Pool.Get
Get流程
func (p *Pool) Get() any {l, pid := p.pin()x := l.privatel.private = nilif x == nil {x, _ = l.shared.popHead()if x == nil {x = p.getSlow(pid)}}runtime_procUnpin()if x == nil && p.New != nil {x = p.New()}return x
}
• 调用 Pool.pin 方法,绑定当前 goroutine 与 P,并且取得该 P 对应的缓存数据;
• 尝试获取 P 缓存数据的私有元素 private;
• 倘若前一步失败,则尝试取 P 缓存数据中共享元素链表的头元素;
• 倘若前一步失败,则走入 Pool.getSlow 方法,尝试取其他 P 缓存数据中共享元素链表的尾元素;
• 同样在 Pool.getSlow 方法中,倘若前一步失败,则尝试从上轮 gc 前缓存中取元素(victim);
• 调用 native 方法解绑 当前 goroutine 与 P
• 倘若(2)-(5)步均取值失败,调用用户的工厂方法,进行元素构造并返回.
3.2.2.1 Pool.Put
Put流程
/ Put adds x to the pool.
func (p *Pool) Put(x any) {if x == nil {return}l, _ := p.pin()if l.private == nil {l.private = x} else {l.shared.pushHead(x)}runtime_procUnpin()
}
• 判断存入元素 x 非空;
• 调用 Pool.pin 绑定当前 goroutine 与 P,并获取 P 的缓存数据;
• 倘若 P 缓存数据中的私有元素为空,则将 x 置为其私有元素;
• 倘若未走入(3)分支,则将 x 添加到 P 缓存数据共享链表的末尾;
• 解绑当前 goroutine 与 P.
3.2.2 对象池的回收
存入 pool 的对象会不定期被 go 运行时回收,因此 pool 没有容量概念,即便大量存入元素,也不会发生内存泄露.
具体回收时机是在 gc 时执行的:
• 每个 Pool 首次执行 Get 方法时,会在内部首次调用 pinSlow 方法内将该 pool 添加到迁居的 allPools 数组中;
• 每次 gc 时,会将上一轮的 oldPools 清空,并将本轮 allPools 的元素赋给 oldPools,allPools 置空;
• 新置入 oldPools 的元素统一将 local 转移到 victim,并且将 local 置为空.
综上可以得见,最多两轮 gc,pool 内的对象资源将会全被回收.
参考作者 : 小徐先生的编程世界
相关文章:

Go语言底层(三): sync 锁 与 对象池
1. 背景 在并发编程中,正确地管理共享资源是构建高性能程序的关键。Go 语言标准库中的 sync 包提供了一组基础而强大的并发原语,用于实现安全的协程间同步与资源控制。本文将简要介绍 sync 包中常用的类型和方法: sync 锁 与 对象池,帮助开发…...
登高架设作业操作证考试:理论题库高频考点有哪些?
一、安全基础知识 法律法规 《安全生产法》《特种作业人员安全技术培训考核管理规定》中关于登高作业的强制性要求(如持证上岗、培训时限等)。 事故责任划分:未系安全带、无监护作业等违规行为的法律后果。 个人防护 安全带使用标准&#…...

2025年06月06日Github流行趋势
项目名称:agent-zero 项目地址url:https://github.com/frdel/agent-zero项目语言:Python历史star数:8958今日star数:324项目维护者:frdel, 3clyp50, linuztx, evrardt, Jbollenbacher项目简介:A…...
华为云CentOS配置在线yum源,连接公网后,逐步复制粘贴,看好自己对应的版本即可,【新手必看】
华为云镜像源配置 YUM 源的详细步骤: 1. 备份原有的 YUM 源配置文件 在修改 YUM 源之前,建议备份原有的配置文件。通常,YUM 源的配置文件位于 /etc/yum.repos.d/ 目录下。例如,备份 CentOS 的默认 YUM 源配置文件: …...
http头部注入攻击
1.HTTP请求的组成部分 HTTP(HyperText Transfer Protocol)请求由 请求行(Request Line)、请求头(Headers)、空行(Blank Line)和请求体(Request Body) 组成。具体结构如下: 1. 请求行(Request Line) 请求行是HTTP请求的第一行,包含三个部分…...
三类 Telegram 账号的风控差异分析与使用建议
在使用 Telegram 过程中,很多用户会遇到账号被限制、封禁、加群失败等问题。除了操作行为外,账号本身的注册方式、活跃时间、环境匹配程度也会直接影响风控等级。 本篇文章从账号风控角度出发,分析三类常见 Telegram 账号的特点与适用环境&am…...
Matlab | matlab中的点云处理详解
点云处理 ⚙️ **一、点云基础操作**🧹 **二、点云预处理**📊 **三、特征提取与分析**🔄 **四、点云配准(对齐点云)**🔷 **五、三维重建与应用**⚡️ **六、高级功能与性能优化**💎 **七、实战技巧与参数调优**📚 **学习资源**MATLAB 的点云处理能力主要依赖 Poi…...
【机试题解法笔记】寻找最大价值的矿堆
题目 给你一个由 0(空地)、1(银矿)、2(金矿) 组成的的地图,矿堆只能由上下左右相邻的金矿或银矿连接形成。超出地图范围可以认为是空地。 假设银矿价值 1,金矿价值 2,请你找出地图中最大价值的矿堆并输出该矿堆的价值。 输入描述 地图元素信…...

动态规划 熟悉30题 ---上
本来是要写那个二维动态规划嘛,但是我今天在问题时候,一个大佬就把他初一时候教练让他练dp的30题发出来了(初一,啊虽然知道计算机这一专业,很多人从小就学了,但是我每次看到一些大佬从小学还是会很羡慕吧或…...
嵌入式学习笔记- freeRTOS 带FromISR后缀的函数
FreeRTOS中带FromISR后缀的函数 是用于中断的函数,它有两个特点 一个是无等待延时, 一个是无立刻触发任务切换, 那么 一 为什么中断中不能等待(阻塞)? 因为中断中等待的,一般都是任务给予的…...

Linux系统:ELF文件的定义与加载以及动静态链接
本节重点 ELF文件的概念与结构可执行文件,目标文件ELF格式的区别ELF文件的形成过程ELF文件的加载动态链接与静态链接动态库的编址与方法调用 一、ELF文件的概念与结构 1.1 文件概述 ELF(Executable and Linkable Format)即“可执行与可链…...
迷宫与陷阱--bfs+回路+剪枝
1.用bfs板子,同时会出现回路,但不能不用bo数组,要减去一部分没有用的回路 2.什么叫没有用的回路--因为我有无敌了,以前遇到的陷阱就能过了,那这就是有用的回路, 所以我记录(x,y)点…...

【国产化适配】如何选择高效合规的安全数据交换系统?
一、安全数据交换系统的核心价值与国产化需求 在数字化转型浪潮中,企业数据流动的频率与规模呈指数级增长,跨网文件传输已成为日常运营的刚需,所以安全数据交换系统也是企业必备的工具。然而,数据泄露事件频发、行业合规要求趋严…...
基于深度学习的裂缝检测与分割研究方向的 数据集介绍
目录 一、基于深度学习的裂缝检测与分割研究方向 1. 任务定义与挑战 2. 主流方法与技术演进 3. 实际应用优化 二、裂缝检测与分割常用数据集详解 1. SDNET2018 2. CrackTree(CrackTree200) 3. AigleRN 4. CFD(Concrete Crack Detect…...
【Prompt实战】国际翻译小组
本文原创作者:姚瑞南 AI-agent 大模型运营专家/音乐人/野生穿搭model,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。&#…...

简化复杂系统的优雅之道:深入解析 Java 外观模式
一、外观模式的本质与核心价值 在软件开发的世界里,我们经常会遇到这样的场景:一个复杂的子系统由多个相互协作的类组成,这些类之间可能存在错综复杂的依赖关系和交互逻辑。当外部客户端需要使用这个子系统时,往往需要了解多个类…...

设计模式杂谈-模板设计模式
在进入正题之前,先引入这样一个场景: 程序员A现在接到这样一个需求:这个需求有10个接口,这些接口都需要接收前端的传参,以及给前端返回业务状态信息。出于数据保密的要求,不管是前端传参还是最终参数返回都…...
LangChain【8】之工具包深度解析:从基础使用到高级实践
文章目录 1. LangChain工具包概述1.1 工具包的基本概念1.2 工具包的主要类型 2. SQL数据库工具包深度解析2.1 基本配置与初始化2.2 数据库连接与验证2.3 工具包初始化与工具获取2.4 创建Agent并执行查询2.5 完整代码 3. 高级使用技巧3.1 自定义工具集成3.2 多工具包组合使用3.3…...

C#入门学习笔记 #6(字段、属性、索引器、常量)
欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 将这四种成语放在一起讲是因为这四种成员都是用来表达数据的。 字段…...

广目软件GM DC Monitor
广目(北京)软件有限公司成立于2024年,技术和研发团队均来自于一家具有近10年监控系统研发的企业。广目的技术团队一共实施了9家政府单位、1家股份制银行、1家芯片制造企业的数据中心监控预警项目。这11家政企单位由2家正部级、1家副部级、6家…...

每日八股文6.6
每日八股-6.6 Mysql1.怎么查看一条sql语句是否走了索引?2.能说说 MySQL 事务都有哪些关键特性吗?3.MySQL 是如何保证事务的原子性的?4.MySQL 是如何保证事务的隔离性的?5.能简单介绍一下 MVCC 吗?或者说,你…...
动静态库的使用(Linux下)
1.库 通俗来说,库就是现有的,可复用的代码,例如:在C/C语言编译时,就需要依赖相关的C/C标准库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。通常我们可以在windows下看到一些后…...

PostgreSQL17 编译安装+相关问题解决
更新时间:2025.6.6,当前最新稳定版本17.5,演示的是17.5,最新测试版本18beta1 演示系统:debian12 很多时候,只有编译安装才能用上最新的软件版本或指定的版本。这也是编译安装的意义。 一、编译安装 &…...
FFMPEG 提取视频中指定起始时间及结束时间的视频,给出ffmpeg 命令
以下是提取视频中指定起始时间及结束时间的 ffmpeg 命令示例: bash 复制 ffmpeg -i input.mp4 -ss 00:01:30.00 -to 00:05:00.00 -c copy output.mp4 其中,-i input.mp4 是指定要处理的输入视频文件为 “input.mp4”。 -ss 00:01:30.00 表示指定视频的起始时间为 1 分 30 …...

React 第五十六节 Router 中useSubmit的使用详解及注意事项
前言 useSubmit 是 React Router v6.4 引入的强大钩子,用于以编程方式提交表单数据。 它提供了对表单提交过程的精细控制,特别适合需要自定义提交行为或非标准表单场景的应用。 一、useSubmit 核心用途 编程式表单提交:不依赖 <form>…...

华为云学堂-云原生开发者认证课程列表
华为云学堂-云原生认证 云原生开发者认证的前5个课程...
Vue.js 组件:深入理解与实践
Vue.js 组件:深入理解与实践 引言 随着前端技术的不断发展,Vue.js 作为一种流行的前端框架,因其简洁、易学、高效的特点受到越来越多开发者的青睐。在Vue.js中,组件是构建用户界面的基石。本文将深入探讨Vue.js组件的概念、特性、创建方式以及在实际开发中的应用,帮助读…...
什么是强化学习:设置奖励函数最为loss, 监督学习:标签准确率作为loss
什么是强化学习:设置奖励函数最为loss, 监督学习:标签准确率作为loss 什么是强化学习:在复杂环境中自主探索,适用于序列决策 最大优势: 通过试错探索发现最优策略,适应环境动态变化,擅长解决需要长期规划和序列决策的问题。典型案例: 游戏AI(如AlphaGo/AlphaZero):…...

理解网络协议
1.查看网络配置 : ipconfig 2. ip地址 : ipv4(4字节, 32bit), ipv6, 用来标识主机的网络地址 3.端口号(0~65535) : 用来标识主机上的某个进程, 1 ~ 1024 知名端口号, 如果是服务端的话需要提供一个特定的端口号, 客户端的话是随机分配一个端口号 4.协议 : 简单来说就是接收数据…...
placeholder不显示and模板字符串无效
一、placeholder"请输入"不显示请输入? input框里写了placeholder为什么不显示呢? 检查代码,input是否有初始值 在 Vue.js 中,v-model 是双向绑定的语法糖,它会动态更新输入框的 value。如果绑定的数据有初…...