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包提供的同步原语。它的目的是确保一段代码只执行一次,而不管有多少协程试图执行它。这听起来可能很简单,但它改变了并发环境中管理一次性操作的规则。…...
react关于手搓antd pro面包屑的经验(写的不好请见谅)
我们先上代码,代码里面都有注释,我是单独写了一个组件,方便使用,在其他页面引入就行了 还使用了官方的Breadcrumb组件 import React, { useEffect, useState } from react; import { Breadcrumb, Button } from antd; import { …...
Android修行手册-五种比较图片相似或相同
Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材…...
设计模式.
设计模式 一、介绍二、六大原则1、单一职责原则(Single Responsibility Principle, SRP)2、开闭原则(Open-Closed Principle, OCP)3、里氏替换原则(Liskov Substitution Principle, LSP)4、接口隔离原则&am…...
使用PyCharm创建项目以及如何注释代码
创建好项目后会出现如下图所示的画面,我们可以通过在项目文件夹上点击鼠标右键,选择“New”菜单下的“Python File”来创建一个 Python 文件,在给文件命名时建议使用英文字母和下划线的组合,创建好的 Python 文件会自动打开&#…...
LabVIEW与PLC交互
一、写法 写命令立即读出 写命令后立即读出,在同一时间不能有多个地方写入,因此需要在整个写入后读出过程加锁 项目中会存在多个循环并行执行该VI,轮询PLC指令 在锁内耗时,就是TCP读写的实际耗时为5-8ms,在主VI六个…...
Idea 2024.3 使用CodeGPT插件整合Deepseek
哈喽,大家好,我是浮云,最近国产大模型Deepseek异常火爆,作为程序员我也试着玩了一下,首先作为简单的使用,大家进入官网,点击开始对话即可进行简单的聊天使用,点击获取手机app即可安装…...
[论文笔记] Deepseek-R1R1-zero技术报告阅读
启发: 1、SFT&RL的训练数据使用CoT输出的格式,先思考再回答,大大提升模型的数学与推理能力。 2、RL训练使用群体相对策略优化(GRPO),奖励模型是规则驱动,准确性奖励和格式化奖励。 1. 总体概述 背景与目标 报告聚焦于利用强化学习(RL)提升大型语言模型(LLMs)…...
VUE之组件通信(三)
1、$refs与$parent 1)概述: $refs用于:父——>子。$parent用于:子——>父。 2)原理如下: 属性说明$refs值为对象,包含所有被ref属性标识的DOM元素或组件实例。$parent值为对象&#x…...
【Redis实战】投票功能
1. 前言 现在就来实践一下如何使用 Redis 来解决实际问题,市面上很多网站都提供了投票功能,比如 Stack OverFlow 以及 Reddit 网站都提供了根据文章的发布时间以及投票数计算出一个评分,然后根据这个评分进行文章的展示顺序。本文就简单演示…...
linux常用基础命令 最新1
常用命令 查看当前目录下个各个文件大小查看当前系统储存使用情况查看当前路径删除当前目录下所有包含".log"的文件linux开机启动jar更改自动配置文件后操作关闭自启动linux静默启动java服务查询端口被占用查看软件版本重启关机开机启动取别名清空当前行创建文件touc…...
UnityShader学习笔记——多种光源
——内容源自唐老狮的shader课程 目录 1.光源类型 2.判断光源类型 2.1.在哪判断 2.2.如何判断 3.光照衰减 3.1.基本概念 3.2.unity中的光照衰减 3.3.光源空间变换矩阵 4.点光源衰减计算 5.聚光灯衰减计算 5.1.聚光灯的cookie(灯光遮罩) 5.2.聚…...
深入浅出谈VR(虚拟现实、VR镜头)
1、VR是什么鬼? 近两年VR这次词火遍网上网下,到底什么是VR?VR是“Virtual Reality”,中文名字是虚拟现实,是指采用计算机技术为核心的现代高科技手段生成一种虚拟环境,用户借助特殊的输入/输出设备&#x…...
项目2 车牌检测
检测车牌 1. 基本思想2. 基础知识2.1 YOLOV5(参考鱼苗检测)2.1.1 模型 省略2.1.2 输入输出 省略2.1.3 损失函数 省略2.2 LPRNet2.2.1 模型2.2.2 输入输出2.2.3 损失函数3. 流程3.1 数据处理3.1.1 YOLOV5数据处理3.2.2 LPRNet数据处理3.2 训练3.2.1 YOLOV5训练 省略3.2.2 LPRN…...
Linux: 网络基础
1.协议 为什么要有协议:减少通信成本。所有的网络问题,本质是传输距离变长了。 什么是协议:用计算机语言表达的约定。 2.分层 软件设计方面的优势—低耦合。 一般我们的分层依据:功能比较集中,耦合度比较高的模块层…...
【实战篇】巧用 DeepSeek,让 Excel 数据处理更高效
一、为何选择用 DeepSeek 处理 Excel 在日常工作与生活里,Excel 是我们频繁使用的工具。不管是统计公司销售数据、分析学生成绩,还是梳理个人财务状况,Excel 凭借其强大的功能,如数据排序、筛选和简单公式计算,为我们提供了诸多便利。但当面对复杂的数据处理任务,比如从…...
Flink CDC YAML:面向数据集成的 API 设计
摘要:本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江(雪尽)老师在 Flink Forward Asia 2024 数据集成(一)专场中的分享。主要分为以下四个方面: Flink CDC YAML API Transform A…...
RabbitMQ技术深度解析:打造高效消息传递系统
引言 在当前的分布式系统架构中,消息队列作为一种高效的消息传递机制,扮演着越来越重要的角色。RabbitMQ,作为广泛使用的开源消息代理,以其高可用性、扩展性和灵活性赢得了众多开发者的青睐。本文将深入探讨RabbitMQ的核心概念、…...
DeepSeek与人工智能的结合:探索搜索技术的未来
云边有个稻草人-CSDN博客 目录 引言 一、DeepSeek的技术背景 1.1 传统搜索引擎的局限性 1.2 深度学习在搜索中的优势 二、DeepSeek与人工智能的结合 2.1 自然语言处理(NLP) 示例代码:基于BERT的语义搜索 2.2 多模态搜索 示例代码&…...
TAPEX:通过神经SQL执行器学习的表格预训练
摘要 近年来,语言模型预训练的进展通过利用大规模非结构化文本数据取得了巨大成功。然而,由于缺乏大规模高质量的表格数据,在结构化表格数据上应用预训练仍然是一个挑战。本文提出了TAPEX,通过在一个合成语料库上学习神经SQL执行…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
