Go中sync.Once源码的深度讲解

2025-01-28 16:50
文章标签 源码 讲解 go 深度 sync

本文主要是介绍Go中sync.Once源码的深度讲解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有...

概念

sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次。它在多线程环境中非常有用,尤其是在需要初始化共享资源或执行某些一次性任务时。

简单示例

当我们在web服务访问某个路由时,如果需要事先获取某些配置,往往会写一个loadConfig函数,获取一个cfg配置项。多次路由访问所需要获取的配置项通常是相同的,如果对于每次路由访问,都加载一次loadConfig函数,会导致产生一些不必要的开销。如果loadConfig涉及到读取文件、解析配置、网络请求时,有可能会额外增加的请求响应时间,降低服务的吞吐量。使用sync.Once包提供的Do函数,就可以只在第一次请求时调用loadConfig函数加载配置,之后的请求都复用第一次请求的配置,缩短响应时间。

package main

import (
    "log"
    "net/http"
    "sync"
)

type Config struct {
    APIKey   string
    LogLevel string
}

varjavascript (
    config *Config
    once   sync.Once
)

func loadConfig() {
    // 模拟从文件或环境变量加载配置
    config = &Config{
       APIKey:   "secret-key",
       LogLevel: "debug",
    }
    log.Println("Configuration loaded")
}

func GetConfig() *Config {
    onchttp://www.chinasem.cne.Do(loadConfig) // 仅第一次访问时会执行loadConfig函数
    return config
}

func handler(w http.ResponseWriter, r *http.Request) {
    cfg := GetConfig()
    log.Printf("Request handled with API key: %s", cfg.APIKey)
    w.Write([]byte("OK"))
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8888", nil))
}

源码解读

源码文件:src/sync/once.go (go 1.23 版本)

package sync

import (
    "sync/atomic"
)

type Once struct {
    done atomic.Uint32 // 是否已执行标识位,0-未执行 1-已执行
    m    Mutex         // 互斥锁,确保并发安全
}

func (o *Once) Do(f func()) {
    // 第一次执行Do函数时,原子操作检查o.done==0,执行DOSlow函数后,o.done==1
    // 第二次及之后执行Do函数,原子操作检查o.done标识位为1,Do函数不执行任何功能,确保了f函数只在第一次被执行
    if o.done.Load() == 0 {
       o.doSljavascriptow(f) // 调用doSlow函数执行f方法。第一次执行时,同一时间可能有多个goroutine尝试同时执行doSlow函数
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock() // 加锁保护,避免多个goroutine同时绕过之前的原子操作检查,并发修改o.done的值
    defer o.m.Unlock()
    // 二次检查o.done的值,同一时间并发执行doSlow函数的goroutine,在第一个goroutine将o.done置为1并解除互斥锁后,
    // 剩下的goroutine识别到自身的o.done已经被设为1,无法绕过二次检查
    if o.done.Load() == 0 { 
       defer o.done.Store(1) // 需要在f()函数执行完成之后,原子性地将o.done设为1
       f() // 执行f方法,一定只有一个goroutine会调用这个方法
    }
}

可以看到,once.go文件的代码非常精炼。仅定义了一个含2个非导出字段donem的结构体Once,并提供了一个doSlow方法用于执行f函数。当我们调用Do方法时,程序经历了几个关键步骤:

  • 判断done标志位是否等于0,如果是,说明f函数还没有被执行,执行doSlow方法
  • mu互斥锁加锁,防止多个goroutine并发操作
  • double-check done标志位是否等于0,如果是,说明f函数还没有被执行,执行f函数
  • f函数执行完成之后,再将done标志位原子性设为1。使用原子操作是从内存可见性的角度出发,如果done使用uinphpt32而不是atomic.Uint32done修改可能不会立即被其它goroutine感知,解锁后仍有可能存在goroutinedone等于0,重复执行f函数
  • mu互斥锁解锁。此时进入到doSlow函数的其它goroutine也感知到了o.done等于1,不会重复执行f函数了

总结

上文就是针对Go源码sync.Once原理和使用方式的讲解。在实际开发中,sync.Once的使用还是非常普遍的。掌握sync.Once的底层原理,有助于我们在今后的开发中更有把握地利用它永远只执行一次函数的特性,完成复杂的技术需求或者业务需求。

到此这篇关于Go中sync.OChina编程nce源码的深度讲解的文章就介绍到这了,更多相关Go sync.Once内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于Go中sync.Once源码的深度讲解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153251

相关文章

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

Maven中生命周期深度解析与实战指南

《Maven中生命周期深度解析与实战指南》这篇文章主要为大家详细介绍了Maven生命周期实战指南,包含核心概念、阶段详解、SpringBoot特化场景及企业级实践建议,希望对大家有一定的帮助... 目录一、Maven 生命周期哲学二、default生命周期核心阶段详解(高频使用)三、clean生命周期核心阶

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

Go之errors.New和fmt.Errorf 的区别小结

《Go之errors.New和fmt.Errorf的区别小结》本文主要介绍了Go之errors.New和fmt.Errorf的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考... 目录error的基本用法1. 获取错误信息2. 在条件判断中使用基本区别1.函数签名2.使用场景详细对

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作