Golang 并发机制-7:sync.Once实战应用指南
Go的并发模型是其突出的特性之一,但强大的功能也带来了巨大的责任。sync.Once是由Go的sync包提供的同步原语。它的目的是确保一段代码只执行一次,而不管有多少协程试图执行它。这听起来可能很简单,但它改变了并发环境中管理一次性操作的规则。
sync.Once介绍
Go的并发模型是其突出的特性之一,但强大的功能也带来了巨大的责任。sync.Once是由Go的sync包提供的同步原语。它的目的是确保一段代码只执行一次,而不管有多少协程试图执行它。这听起来可能很简单,但它改变了并发环境中管理一次性操作的规则。
- 定义: sync.Once是一个结构体,只有一个方法Do( f func() )
- 目的:它保证函数f最多被调用一次,即使Do被并发地调用了多次
- 线程安全: sync.Once是完全线程安全的,因此非常适合并发程序

但是ync.Once与其他同步原语有那些差异?与mutex或channel不同,它们可以重复使用,sync.Once是专门为一次性行为设计的。它是轻量级的,并且针对这一单一目的进行了优化。
同步的常见用例, sync.Once包括:
- 初始化共享资源
- 构建单例模式
- 仅执行单次的昂贵任务
- 加载配置文件
下面是一个简单示例:
var instance *singleton
var once sync.Oncefunc getInstance() *singleton {once.Do(func() {instance = &singleton{}})return instance
}
在这个代码片段中,我们使用了sync.Once确保我们的单例只初始化一次,即使从多个协程并发调用getInstance() 也是如此。
但我们只是触及了表面!sync.Once有更多的实际应用,我们将在本文中深入探讨。从基本示例到高级用法,我们将涵盖所有内容。所以,系好安全带,让我们深入了解sync.Once的世界。
基本sync.Once示例:单例模式
单例模式是一种经典的软件设计模式,它将类的实例化限制为单个实例。当只需要一个对象来协调跨系统的操作时,它特别有用。在Go中,sync.Once提供了一种优雅且线程安全的方式来实现此模式。
让我们深入了解使用同步的具体示例。sync.Once用于单例实现:
package mainimport ("fmt""sync"
)type Singleton struct {data string
}var instance *Singleton
var once sync.Oncefunc GetInstance() *Singleton {once.Do(func() {fmt.Println("Creating Singleton instance")instance = &Singleton{data: "I'm the only one!"}})return instance
}func main() {for i := 0; i < 5; i++ {go func() {fmt.Printf("%p\n", GetInstance())}()}// Wait for goroutines to finishfmt.Scanln()
}
在本例中,我们使用sync.Once以确保我们的Singleton结构只实例化一次,即使在从多个例程并发调用GetInstance()时也是如此。
让我们来分析一下使用同步的好处。对于这个单例实现:
-
线程安全: sync.Once保证初始化函数只调用一次,即使在并发环境中也是如此。这消除了初始化期间的竞争条件。
-
延迟初始化:Singleton实例仅在GetInstance()第一次调用时创建,而不是在程序启动时创建。这对资源管理是有益的。
-
简单性:与其他线程安全的单例实现(如使用互斥锁)相比,sync.Once在Go中提供了一个更干净、更惯用的解决方案。
-
性能:在第一次调用之后,对
once.Do()的后续调用基本上是无操作的,这使得它非常高效。
值得注意的是,虽然singleton很有用,但它们并不总是最好的解决方案。它们会使单元测试变得更加困难,并且违反单一职责原则。始终考虑是否有更适合您的特定用例的设计模式。
下面是对单例实现的快速比较:
| Method | Thread-safe? | Lazy initialization? | Complexity |
|---|---|---|---|
| sync.Once | Yes | Yes | Low |
| Mutex | Yes | Yes | Medium |
| init() function | Yes | No | Low |
| Global variable | No | No | Very Low |
| Method | Thread-safe? | Lazy initialization? | Complexity |
|---|---|---|---|
| sync.Once | Yes | Yes | Low |
| Mutex | Yes | Yes | Medium |
| init() function | Yes | No | Low |
| Global variable | No | No | Very Low |
如你所见,sync.Once在线程安全性、延迟初始化和低复杂性之间提供了很好的平衡。
请记住,虽然这个示例演示了sync.Once的基本用法。曾经,它的应用远远超出了单例模式。在接下来的部分中,我们将探索更高级的用法和最佳实践。
高级sync.Once用法:延迟初始化
延迟初始化是一种设计模式,在这种模式中,我们将对象的创建、值的计算或其他一些代价高昂的过程延迟到第一次需要的时候。这种策略可以显著提高性能和资源使用,特别是对于初始化成本高的应用程序。在Go中,同步。Once为实现线程安全的延迟初始化提供了一种优秀的机制。
让我们用一个更复杂的例子来探索这个概念:
package mainimport ("database/sql""fmt""log""sync"_ "github.com/lib/pq"
)type DatabaseConnection struct {db *sql.DB
}var (dbConn *DatabaseConnectiononce sync.Once
)func GetDatabaseConnection() (*DatabaseConnection, error) {var initError erroronce.Do(func() {fmt.Println("Initializing database connection...")db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")if err != nil {initError = fmt.Errorf("failed to open database: %v", err)return}if err = db.Ping(); err != nil {initError = fmt.Errorf("failed to ping database: %v", err)return}dbConn = &DatabaseConnection{db: db}})if initError != nil {return nil, initError}return dbConn, nil
}func main() {// Simulate multiple goroutines trying to get the database connectionvar wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)go func(id int) {defer wg.Done()conn, err := GetDatabaseConnection()if err != nil {log.Printf("Goroutine %d: Error getting connection: %v\n", id, err)return}log.Printf("Goroutine %d: Got connection %p\n", id, conn)}(i)}wg.Wait()
}
这个例子演示了几个高级概念:
-
资源密集型初始化:创建数据库连接的成本通常很高。通过使用sync.Once确保这个昂贵的操作只发生一次,而不管有多少协程请求连接。
-
错误处理:我们已经将错误处理合并到惰性初始化中。如果在初始化期间发生错误,则捕获错误并将其返回给所有调用者。
-
并发管理:该示例模拟了数据库连接的多个并发请求,展示了如何sync.Once有效地管理了这个场景。
-
实际用例:此模式在实际应用程序中通常用于管理共享资源,如数据库连接、配置加载或缓存初始化。
让我们来分析一下使用sync.Once实现延迟初始化的好处:
- 效率:资源只在实际需要时才分配,这可以显著减少启动时间和内存使用。
- 线程安全:sync.Once确保即使多个程序试图同时初始化资源,初始化也只发生一次。
- 简单性:与手动锁定机制相比,sync.Once提供了一种更干净、更不容易出错的方法。
- 关注点分离:初始化逻辑被封装在once.Do()函数中,使代码更模块化,更易于维护。
下面是不同初始化策略的比较:
| Strategy | Pros | Cons |
|---|---|---|
| Eager Initialization | Simple, predictable | Potentially wasteful if resource isn’t used |
| Lazy Initialization (without sync) | Efficient | Not thread-safe |
| Lazy Initialization (with sync.Once) | Efficient, thread-safe | Slightly more complex than eager initialization |
| Lazy Initialization (with mutex) | Flexible, allows re-initialization | More complex, potentially less performant |
特别提醒:sync.Once是强大的,它并不总是最好的解决方案。例如,如果需要重新初始化资源的能力(例如,在连接丢失后重新连接到数据库),你可能需要使用其他同步原语,如mutex.。
在下一节中,我们将探讨使用sync.Once时的常见缺陷和最佳实践,确保您可以在Go程序中有效地利用这个强大的工具。
sync.Once的实际应用
虽然我们已经介绍了同步的一些基本和高级示例。现在,让我们深入了解一些实际应用程序,在这些应用程序中,同步原语真正发挥了作用。这些示例将演示如何sync.Once可以用来解决Go编程中的常见问题,特别是在并发和分布式系统中。
- 数据库连接池
连接池是一种用于提高数据库性能的技术。不是为每个操作打开和关闭连接,而是维护可重用连接池。
import ("database/sql""sync"_ "github.com/lib/pq"
)var (dbPool *sql.DBpoolOnce sync.Once
)func GetDBPool() (*sql.DB, error) {var err errorpoolOnce.Do(func() {dbPool, err = sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")if err != nil {return}dbPool.SetMaxOpenConns(25)dbPool.SetMaxIdleConns(25)dbPool.SetConnMaxLifetime(5 * time.Minute)})if err != nil {return nil, err}return dbPool, nil
}
这种方法确保数据库连接池只初始化一次,而不管有多少协程调用GetDBPool(),它既高效又线程安全。
- 配置加载场景
加载配置文件是sync.Once的另一个常见用例。通常希望在启动时只加载一次,但首次需要惰性加载。
import ("encoding/json""os""sync"
)type Config struct {APIKey string `json:"api_key"`Debug bool `json:"debug"`
}var (config *ConfigconfigOnce sync.Once
)func GetConfig() (*Config, error) {var err errorconfigOnce.Do(func() {file, err := os.Open("config.json")if err != nil {return}defer file.Close()config = &Config{}err = json.NewDecoder(file).Decode(config)})if err != nil {return nil, err}return config, nil
}
此模式确保读取和解析配置文件的潜在昂贵操作只发生一次,即使应用程序的多个部分同时请求配置。
- 模块化Go应用中插件初始化
对于插件架构的应用程序, sync.Once可以用来确保每个插件只初始化一次,即使有多个组件试图使用它:
type Plugin struct {Name stringinitialized boolinitOnce sync.Once
}func (p *Plugin) Initialize() error {var err errorp.initOnce.Do(func() {// Simulate complex initializationtime.Sleep(2 * time.Second)if p.Name == "BadPlugin" {err = fmt.Errorf("failed to initialize plugin: %s", p.Name)}p.initialized = truefmt.Printf("Plugin %s initialized\n", p.Name)})return err
}func UsePlugin(name string) error {plugin := &Plugin{Name: name}if err := plugin.Initialize(); err != nil {return err}// Use the plugin...return nil
}
这种方法允许延迟加载插件,并确保即使多个线程试图同时使用同一个插件,初始化也只发生一次。
- 几种方式优缺点比较
| Use Case | Benefits of sync.Once | Potential Drawbacks |
|---|---|---|
| DB Connection Pooling | Ensures single pool creation, thread-safe | May delay error detection until first use |
| Config Loading | Lazy loading, consistent config across app | Might complicate dynamic config updates |
| Plugin Initialization | Efficient for rarely used plugins | Could increase complexity in plugin management |
这些实际应用程序展示了 sync.Once 的多功能性。解决常见的并发编程挑战。通过理解这些模式,你可以适时、高效使用sync.Once,让应用更高效和健壮的代码。
最后总结
本文介绍了Golang sync.Once的最佳实践,从基本示例到高级应用程序。记住,sync.Once是你在并发Go程序中“执行一次且仅执行一次”场景的首选工具。它很简单,但功能强大——就像go本身一样。因此,下次遇到一次性初始化挑战时,你将确切地知道应该使用什么方法。
相关文章:
Golang 并发机制-7:sync.Once实战应用指南
Go的并发模型是其突出的特性之一,但强大的功能也带来了巨大的责任。sync.Once是由Go的sync包提供的同步原语。它的目的是确保一段代码只执行一次,而不管有多少协程试图执行它。这听起来可能很简单,但它改变了并发环境中管理一次性操作的规则。…...
【AI实践】Cursor上手-跑通Hello World和时间管理功能
背景 学习目的:熟悉Cursor使用环境,跑通基本开发链路。 本人背景:安卓开发不熟悉,了解科技软硬件常识 实践 基础操作 1,下载安装安卓Android Studio 创建一个empty project 工程,名称为helloworld 2&am…...
深度学习 视频推荐
以下为你呈现一个基于深度学习实现视频推荐的简化代码示例。这里我们使用的是协同过滤思想结合神经网络的方式,借助 TensorFlow 和 Keras 库来构建模型。在这个示例中,假设已有用户对视频的评分数据,目标是预测用户对未评分视频的评分,进而为用户推荐可能感兴趣的视频。 1…...
缓存组件<keep-alive>
缓存组件<keep-alive> 1.组件作用 组件, 默认会缓存内部的所有组件实例,当组件需要缓存时首先考虑使用此组件。 2.使用场景 场景1:tab切换时,对应的组件保持原状态,使用keep-alive组件 使用:KeepAlive | Vu…...
SpringBoot单机模式的极限是什么?为什么会引入分布式?
Spring Boot 单机模式的极限 Spring Boot 单机模式的极限主要体现在以下几个方面: 硬件资源限制: CPU:单机性能受限于 CPU 核心数和主频,无法无限扩展。内存:内存容量有限,无法应对大规模数据处理或高并发…...
【多模态大模型】系列4:目标检测(ViLD、GLIP)
目录 1 ViLD2 GLIP 1 ViLD OPEN-VOCABULARY OBJECT DETECTION VIA VISION AND LANGUAGE KNOWLEDGE DISTILLATION 从标题就能看出来,作者是把CLIP模型当成一个Teacher,去蒸馏他自己的网络,从而能Zero Shot去做目标检测。 现在的目标检测数据…...
计算机网络结课设计:通过思科Cisco进行中小型校园网搭建
上学期计算机网络课程的结课设计是使用思科模拟器搭建一个中小型校园网,当时花了几天时间查阅相关博客总算是做出来了,在验收后一直没管,在寒假想起来了简单分享一下,希望可以给有需求的小伙伴一些帮助 目录 一、设计要求 二、…...
从零到一:基于Rook构建云原生Ceph存储的全面指南(下)
接上篇:《从零到一:基于Rook构建云原生Ceph存储的全面指南(上)》 链接: link 六.Rook部署云原生CephFS文件系统 6.1 部署cephfs storageclass cephfs文件系统与RBD服务类似,要想在kubernetes pod里使用cephfs&#…...
mysql的语句备份详解
使用mysqldump工具备份(适用于逻辑备份) mysqldump是 MySQL 自带的一个非常实用的逻辑备份工具,它可以将数据库中的数据和结构以 SQL 语句的形式导出到文件中。 1. 备份整个数据库 mysqldump -u [用户名] -p [数据库名] > [备份文件名].…...
AutoMQ 如何实现没有写性能劣化的极致冷读效率
前言 追赶读(Catch-up Read,冷读)是消息和流系统常见和重要的场景。 削峰填谷:对于消息来说,消息通常用作业务间的解耦和削峰填谷。削峰填谷要求消息队列能将上游发送的数据堆积住,让下游在容量范围内消费…...
【Rabbitmq篇】高级特性----TTL,死信队列,延迟队列
目录 一.TTL ???1.设置消息的TTL 2.设置队列的TTL 3.俩者区别? 二.死信队列 定义: 消息成为死信的原因: 1.消息被拒绝(basic.reject 或 basic.nack) 2.消息过期(TTL) 3.队列达到最大长度? …...
【Java】多线程和高并发编程(三):锁(中)深入ReentrantLock
文章目录 3、深入ReentrantLock3.1 ReentrantLock和synchronized的区别3.2 AQS概述3.3 加锁流程源码剖析3.3.1 加锁流程概述3.3.2 三种加锁源码分析3.3.2.1 lock方法3.3.2.2 tryLock方法3.3.2.3 lockInterruptibly方法 3.4 释放锁流程源码剖析3.4.1 释放锁流程概述3.4.2 释放锁…...
Unity 高度可扩展的技能与多 Buff 框架详解
一、框架设计 1.1 核心思想 组件化设计: 将技能和 Buff 抽象为可复用的组件,通过组合不同的组件实现复杂的效果。 数据驱动: 使用 ScriptableObject 或 JSON 等数据格式定义技能和 Buff 的属性,方便配置和修改。 事件驱动: 利用 Unity 的事件系统或自…...
电路笔记(元器件):AD 5263数字电位计(暂记)
AD5263 是四通道、15 V、256位数字电位计,可通过SPI/I2C配置具体电平值。 配置模式: W引脚作为电位器的抽头,可在A-B之间调整任意位置的电阻值。也可将W与A(或B)引脚短接,A-W间的电阻总是0欧姆,通过数字接口调整电位器…...
《大规模动画优化(一):GPU 顶点动画的生成》
GPU 顶点动画(Vertex Animation Texture, VAT) GPU 顶点动画(Vertex Animation Texture, VAT)烘焙的核心思想是: 在 CPU 端预先计算动画顶点数据,并存储到纹理(Texture2D)中…...
webpack【初体验】使用 webpack 打包一个程序
打包前 共 3 个文件 dist\index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Webpack 示例&…...
VMware安装CentOS 7(全网超详细图文保姆版教程)
文章目录 一、下载及安装 VMware1.1 VMware下载1.2 CentOS下载 二、搭建虚拟机环境2.1 创建新虚拟机2.2 选择自定义2.3 选择虚拟机硬件兼容性2.4 选择稍后安装操作系统2.5 选择Linux系统 版本选择 centos 7 64位2.6 设备你虚拟机的名字和保存位置(保存位置建议在编辑…...
mysql BUG 导致 show processlist 有大量的show slave stauts 处于init状态
一、详细报错信息: 1、执行show slave status\G 卡住 && stop slave也卡住 2、show processlist 发现 Waiting for commit lock NULL 锁 3、错误日志报错主备同步用户认证失败 二、报错原因(分析过程): 1、排查备库日志…...
机器学习在癌症分子亚型分类中的应用
学习笔记:机器学习在癌症分子亚型分类中的应用——Cancer Cell 研究解析 1. 文章基本信息 标题:Classification of non-TCGA cancer samples to TCGA molecular subtypes using machine learning发表期刊:Cancer Cell发表时间:20…...
从MySQL优化到脑力健康:技术人与效率的双重提升
文章目录 零:前言一:MySQL性能优化的核心知识点1. 索引优化的最佳实践实战案例: 2. 高并发事务的处理机制实战案例: 3. 查询性能调优实战案例: 4. 缓存与连接池的优化实战案例: 二:技术工作者的…...
Qt:项目文件解析
目录 QWidget基础项目文件解析 .pro文件解析 widget.h文件解析 widget.cpp文件解析 widget.ui文件解析 main.cpp文件解析 认识对象模型 窗口坐标系 QWidget基础项目文件解析 .pro文件解析 工程新建好之后,在工程目录列表中有⼀个后缀为 ".pro" …...
react使用if判断
1、第一种 function Dade(req:any){console.log(req)if(req.data.id 1){return <span>66666</span>}return <span style{{color:"red"}}>8888</span>}2、使用 {win.map((req,index) > ( <> <Dade data{req}/>{req.id 1 ?…...
conda 修复 libstdc++.so.6: version `GLIBCXX_3.4.30‘ not found 简便方法
ImportError: /data/home/hum/anaconda3/envs/ipc/bin/../lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /home/hum/anaconda3/envs/ipc/lib/python3.11/site-packages/paddle/base/libpaddle.so) 1. 检查版本 strings /data/home/hum/anaconda3/envs/ipc/…...
在服务器部署JVM后,如何评估JVM的工作能力,比如吞吐量
在服务器部署JVM后,评估其工作能力(如吞吐量)可以通过以下步骤进行: 1. 选择合适的基准测试工具 JMH (Java Microbenchmark Harness):适合微基准测试,测量特定代码片段的性能。Apache JMeter:…...
python学opencv|读取图像(六十)先后使用cv2.erode()函数和cv2.dilate()函数实现图像处理
【1】引言 前序学习进程中,先后了解了使用cv2.erode()函数和cv2.dilate()函数实现图像腐蚀和膨胀处理的效果,相关文章链接为: python学opencv|读取图像(五十八)使用cv2.erode()函数实现图像腐蚀处理-CSDN博客 pytho…...
Itext源代码阅读(2) -- PdfReader
本文基于Itext 5,Itext7相较itext5虽然有较大变化,但是原理是一样的。 参考资料: 使用iText处理pdf文件的入门级教程_itextpdf 教程-CSDN博客 比较详实的介绍了长用的itext 的pdf处理。 深入iText7:第5章源代码实践指南-CSDN博…...
JavaScript-Object 对象的相关方法
1. Object.getPrototypeOf() Object.getPrototypeOf方法返回参数对象的原型。这是获取原型对象的标准方法。 var F function () {}; var f new F(); Object.getPrototypeOf(f) F.prototype // true 上面代码中,实例对象 f的原型是 F.prototype。 下面是几种特殊对…...
Flink 内存模型各部分大小计算公式
Flink 的运行平台 如果 Flink 是运行在 yarn 或者 standalone 模式的话,其实都是运行在 JVM 的基础上的,所以首先 Flink 组件运行所需要给 JVM 本身要耗费的内存大小。无论是 JobManager 或者 TaskManager ,他们 JVM 内存的大小都是一样的&a…...
每日一题——缺失的第一个正整数
缺失的第一个正整数 题目描述进阶:数据范围: 示例示例 1示例 2示例 3 题解思路代码实现代码解释复杂度分析总结 题目描述 给定一个无重复元素的整数数组 nums,请你找出其中没有出现的最小的正整数。 进阶: 时间复杂度ÿ…...
Qt修仙之路2-1 仿QQ登入 法宝初成
widget.cpp #include "widget.h" #include<QDebug> //实现槽函数 void Widget::login1() {QString userusername_input->text();QString passpassword_input->text();//如果不勾选无法登入if(!check->isChecked()){qDebug()<<"xxx"&…...
