返回顶部
首页 > 资讯 > 后端开发 > GO >Golang并发利器sync.Once怎么使用
  • 537
分享到

Golang并发利器sync.Once怎么使用

2023-07-06 00:07:08 537人浏览 薄情痞子
摘要

这篇文章主要介绍了golang并发利器sync.Once怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang并发利器sync.Once怎么使用文章都会有所收获,下面我们一起来看看吧。sync.On

这篇文章主要介绍了golang并发利器sync.Once怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang并发利器sync.Once怎么使用文章都会有所收获,下面我们一起来看看吧。

sync.Once 基本概念

什么是 sync.Once

sync.OnceGo 语言中的一种同步原语,用于确保某个操作或函数在并发环境下只被执行一次。它只有一个导出的方法,即 Do,该方法接收一个函数参数。在 Do 方法被调用后,该函数将被执行,而且只会执行一次,即使在多个协程同时调用的情况下也是如此。

sync.Once 的应用场景

sync.Once 主要用于以下场景:

  • 单例模式:确保全局只有一个实例对象,避免重复创建资源。

  • 延迟初始化:在程序运行过程中需要用到某个资源时,通过 sync.Once 动态地初始化该资源。

  • 只执行一次的操作:例如只需要执行一次的配置加载、数据清理等操作。

sync.Once 应用实例

单例模式

在单例模式中,我们需要确保一个结构体只被初始化一次。使用 sync.Once 可以轻松实现这一目标。

package mainimport (   "fmt"   "sync")type Singleton struct{}var (   instance *Singleton   once     sync.Once)func GetInstance() *Singleton {   once.Do(func() {      instance = &Singleton{}   })   return instance}func main() {   var wg sync.WaitGroup   for i := 0; i < 5; i++ {      wg.Add(1)      go func() {         defer wg.Done()         s := GetInstance()         fmt.Printf("Singleton instance address: %p\n", s)      }()   }   wg.Wait()}

上述代码中,GetInstance 函数通过 once.Do() 确保 instance 只会被初始化一次。在并发环境下,多个协程同时调用 GetInstance 时,只有一个协程会执行 instance = &Singleton{},所有协程得到的实例 s 都是同一个。

延迟初始化

有时候希望在需要时才初始化某些资源。使用 sync.Once 可以实现这一目标。

package mainimport (   "fmt"   "sync")type Config struct {   config map[string]string}var (   config *Config   once   sync.Once)func GetConfig() *Config {   once.Do(func() {      fmt.Println("init config...")      config = &Config{         config: map[string]string{            "c1": "v1",            "c2": "v2",         },      }   })   return config}func main() {   // 第一次需要获取配置信息,初始化 config   cfg := GetConfig()   fmt.Println("c1: ", cfg.config["c1"])   // 第二次需要,此时 config 已经被初始化过,无需再次初始化   cfg2 := GetConfig()   fmt.Println("c2: ", cfg2.config["c2"])}

在这个示例中,定义了一个 Config 结构体,它包含一些设置信息。使用 sync.Once 来实现 GetConfig 函数,该函数在第一次调用时初始化 Config。这样,我们可以在真正需要时才初始化 Config,从而避免不必要的开销。

sync.Once 实现原理

type Once struct {   // 表示是否执行了操作   done uint32   // 互斥,确保多个协程访问时,只能一个协程执行操作   m    Mutex}func (o *Once) Do(f func()) {   // 判断 done 的值,如果是 0,说明 f 还没有被执行过   if atomic.LoadUint32(&o.done) == 0 {      // 构建慢路径(slow-path),以允许对 Do 方法的快路径(fast-path)进行内联      o.doSlow(f)   }}func (o *Once) doSlow(f func()) {   // 加锁   o.m.Lock()   defer o.m.Unlock()   // 双重检查,避免 f 已被执行过   if o.done == 0 {      // 修改 done 的值      defer atomic.StoreUint32(&o.done, 1)      // 执行函数      f()   }}

sync.Once 结构体包含两个字段:donemudone 是一个 uint32 类型的变量,用于表示操作是否已经执行过;m 是一个互斥锁,用于确保在多个协程访问时,只有一个协程能执行操作。

sync.Once 结构体包含两个方法:DodoSlowDo 方法是其核心方法,它接收一个函数参数 f。首先它会通过原子操作atomic.LoadUint32(保证并发安全) 检查 done 的值,如果为 0,表示 f 函数没有被执行过,然后执行 doSlow 方法。

doSlow 方法里,首先对互斥锁 m 进行加锁,确保在多个协程访问时,只有一个协程能执行 f 函数。接着再次检查 done 变量的值,如果 done 的值仍为 0,说明 f 函数没有被执行过,此时执行 f 函数,最后通过原子操作 atomic.StoreUint32done 变量的值设置为 1。

为什么会封装一个 doSlow 方法

doSlow 方法的存在主要是为了性能优化。将慢路径(slow-path)代码从 Do 方法中分离出来,使得 Do 方法的快路径(fast-path)能够被内联(inlined),从而提高性能。

为什么会有双重检查(double check)的写法

源码可知,存在两次对 done 的值的判断。

  • 第一次检查:在获取锁之前,先使用原子加载操作 atomic.LoadUint32 检查 done 变量的值,如果 done 的值为 1,表示操作已执行,此时直接返回,不再执行 doSlow 方法。这一检查可以避免不必要的锁竞争。

  • 第二次检查:获取锁之后,再次检查 done 变量的值,这一检查是为了确保在当前协程获取锁期间,其他协程没有执行过 f 函数。如果 done 的值仍为 0,表示 f 函数没有被执行过。

通过双重检查,可以在大多数情况下避免锁竞争,提高性能。

加强的 sync.Once

sync.Once 提供的 Do 方法并没有返回值,意味着如果我们传入的函数如果发生 error 导致初始化失败,后续调用 Do 方法也不会再初始化。为了避免这个问题,我们可以实现一个 类似 sync.Once 的并发原语。

package mainimport (   "sync"   "sync/atomic")type Once struct {   done uint32   m    sync.Mutex}func (o *Once) Do(f func() error) error {   if atomic.LoadUint32(&o.done) == 0 {      return o.doSlow(f)   }   return nil}func (o *Once) doSlow(f func() error) error {   o.m.Lock()   defer o.m.Unlock()   var err error   if o.done == 0 {      err = f()      // 只有没有 error 的时候,才修改 done 的值      if err == nil {         atomic.StoreUint32(&o.done, 1)      }   }   return err}

上述代码实现了一个加强的 Once 结构体。与标准的 sync.Once 不同,这个实现允许 Do 方法的函数参数返回一个 error。如果执行函数没有返回 error,则修改 done 的值以表示函数已执行。这样,在后续的调用中,只有在没有发生 error 的情况下,才会跳过函数执行,避免初始化失败。

sync.Once 的注意事项

死锁

通过分析 sync.Once 的源码,可以看到它包含一个名为 m 的互斥锁字段。当我们在 Do 方法内部重复调用 Do 方法时,将会多次尝试获取相同的锁。但是 mutex 互斥锁并不支持可重入操作,因此这将导致死锁现象。

func main() {   once := sync.Once{}   once.Do(func() {      once.Do(func() {         fmt.Println("init...")      })   })}

初始化失败

这里的初始化失败指的是在调用 Do 方法之后,执行 f 函数的过程中发生 error,导致执行失败,现有的 sync.Once 设计我们是无法感知到初始化的失败的,为了解决这个问题,我们可以实现一个类似 sync.Once 的加强 once,前面的内容已经提供了具体实现。

关于“Golang并发利器sync.Once怎么使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Golang并发利器sync.Once怎么使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网GO频道。

您可能感兴趣的文档:

--结束END--

本文标题: Golang并发利器sync.Once怎么使用

本文链接: https://lsjlt.com/news/356932.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Golang并发利器sync.Once怎么使用
    这篇文章主要介绍了Golang并发利器sync.Once怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang并发利器sync.Once怎么使用文章都会有所收获,下面我们一起来看看吧。sync.On...
    99+
    2023-07-06
  • Golang并发利器sync.Once的用法详解
    目录简介sync.Once 基本概念什么是 sync.Oncesync.Once 的应用场景sync.Once 应用实例单例模式延迟初始化sync.Once 实现原理加强的 sync...
    99+
    2023-05-15
    Golang并发sync.Once Golang sync.Once使用 Golang sync.Once
  • go并发利器sync.Once如何使用
    这篇文章主要介绍了go并发利器sync.Once如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go并发利器sync.Once如何使用文章都会有所收获,下面我们一起来看看吧。1. 简介本文主要介绍 Go ...
    99+
    2023-07-05
  • go并发利器sync.Once使用示例详解
    目录1. 简介2. 基本使用2.1 基本定义2.2 使用方式2.3 使用例子3. 原理4. 使用注意事项4.1 不能将sync.Once作为函数局部变量4.2 不能在once.Do中...
    99+
    2023-03-14
    go并发sync.Once go sync.Once
  • Golang中怎么利用Goroutine控制并发
    这篇文章给大家介绍Golang中怎么利用Goroutine控制并发,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Channel在 Golang 语言中,Channel 不仅可以用于协程之间通信,还可以使用 Channe...
    99+
    2023-06-20
  • Go并发编程之sync.Once使用实例详解
    目录一.序二. 源码分析2.1结构体2.2 接口三. 使用场景案例3.1 单例模式3.2 加载配置文件示例四.总结五. 参考一.序 单从库名大概就能猜出其作用。sync.Once使用...
    99+
    2024-04-02
  • Golang并发之RWMutex怎么使用
    这篇文章主要介绍“Golang并发之RWMutex怎么使用”,在日常操作中,相信很多人在Golang并发之RWMutex怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang并发之RWMutex怎...
    99+
    2023-07-05
  • golang并发模型怎么使用
    Golang的并发模型是通过goroutine和channel来实现的。1. Goroutine: Goroutine是轻量级的线程...
    99+
    2023-08-23
    golang
  • 利用Golang函数利用并发编程的技巧
    利用 golang 实现并发编程:创建 goroutine:使用 go 关键字创建轻量级线程 goroutine。使用通道:通道是 goroutine 间通信的数据结构,可发送和接收值。...
    99+
    2024-04-12
    golang
  • 如何利用Golang进行并发编程
    在现代计算机系统中,随着计算机科学和技术不断创新,多线程和并发已成为当今软件开发中最流行的主题之一。多线程和并发旨在有效利用计算机系统的资源以提高计算效率和性能。在并发编程领域中,Golang是一种强大而流行的程序设计语言,它具有优异的内置...
    99+
    2023-05-14
  • Golang并发编程怎么应用
    这篇文章主要讲解了“Golang并发编程怎么应用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang并发编程怎么应用”吧!1、通过通信共享并发编程是一个很大的主题,这里只提供一些特定于...
    99+
    2023-07-06
  • 探究Golang为何成为高并发利器?
    Golang(Go语言)作为一种新兴的编程语言,在近年来越来越受到程序员的青睐,尤其在高并发场景下表现出色。那么,Golang为何能成为高并发利器呢?接下来,我们将通过具体的代码示例进...
    99+
    2024-03-01
    golang 高并发 利器 go语言
  • golang并发锁如何使用
    这篇文章主要介绍了golang并发锁如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇golang并发锁如何使用文章都会有所收获,下面我们一起来看看吧。如果程序用到的数据是多个groutine之间的交互过程...
    99+
    2023-07-05
  • golang并发锁使用详解
    目录互斥锁 sync.Mutex 读写锁 sync.RWMutex 如果程序用到的数据是多个groutine之间的交互过程中产生的,那么使用上文提到的channe...
    99+
    2023-02-23
    golang并发与锁 Golang 并发死锁 Golang 并发锁
  • golang怎么并发数据
    Go语言是一种以并发编程为设计重点的语言,它具有轻量级线程(即goroutine)和可扩展的通信机制,使得其在处理并发数据时变得非常高效。本文将介绍如何使用goroutine和通道在Go中实现并发数据处理。goroutine是一种轻量级线程...
    99+
    2023-05-14
  • Golang如何利用Goroutine实现高并发处理?
    go语言中的goroutine是一种轻量级线程,可实现高并发处理。使用 go 关键字创建goroutine,如下所示:go func() {}。优势包括:高吞吐量:goroutine并行...
    99+
    2024-05-10
    高并发 golang go语言
  • golang怎么控制并发数
    在Go中,可以使用goroutine和channel来控制并发数。 首先,可以使用make函数创建一个带有指定并发数的channel...
    99+
    2023-10-21
    golang
  • 怎么利用Redis锁解决高并发
    小编给大家分享一下怎么利用Redis锁解决高并发,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!redis技术的使用:redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的...
    99+
    2024-04-02
  • 怎么使用Golang并发读取文件数据并写入数据库
    本篇内容介绍了“怎么使用Golang并发读取文件数据并写入数据库”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!项目结构data文件夹中包含数...
    99+
    2023-07-02
  • GoLang中怎么利用生产者消费者模式解决并发问题
    这期内容当中小编将会给大家带来有关GoLang中怎么利用生产者消费者模式解决并发问题,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在主函数中创建一个管道,接收字符串类型产生数据,把数据放到管道中在管道中取...
    99+
    2023-06-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作