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

相关文章

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接