广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang sync.Once实现单例模式的方法详解
  • 155
分享到

Golang sync.Once实现单例模式的方法详解

摘要

目录1. sync.Once 的原理和实现2. sync.Once 的错误处理3. sync.Once 的嵌套调用4. 并发性能5. 总结Go 语言的 sync 包提供了一系列同步原

Go 语言的 sync 包提供了一系列同步原语,其中 sync.Once 就是其中之一。sync.Once 的作用是保证某个函数只会被执行一次,即使在多个 goroutine 中也不会重复执行。sync.Once 在实际开发中非常常用,例如在单例模式中。

本文将深入探讨 sync.Once 的实现原理和使用方法,帮助大家更好地理解和应用 sync.Once。

1. sync.Once 的原理和实现

golang 的 sync.Once 是一个并发原语,用于确保某个函数在整个程序运行期间只会执行一次。在内部实现中,sync.Once 基于 sync.Mutex 和 sync.Cond,通过互斥和条件变量来实现线程安全和防止重复执行。下面是一个简单的示例:

 package main
 ​
 import (
     "fmt"
     "sync"
 )
 ​
 func main() {
     var once sync.Once
 ​
     // 保证只会执行一次
     once.Do(func() {
         fmt.Println("Hello, World!")
     })
 }

在这个示例中,我们使用 sync.Once 来确保 fmt.Println("Hello, World!") 只会执行一次。如果我们多次调用 once.Do(),只有第一次会真正执行,后续的调用都会直接返回。这种保证只执行一次的机制非常适用于一些需要缓存结果、初始化状态或者注册回调函数等场景。

sync.Once 的实现基于两个核心的概念:互斥锁和条件变量。sync.Once 内部维护了一个状态标志位 done,用于标记函数是否已经被执行过。如果 done 的值为 true,那么 sync.Once 就认为函数已经执行过,后续的调用直接返回;如果 done 的值为 false,那么 sync.Once 就认为函数还没有执行过,然后通过互斥锁和条件变量来保证函数的线程安全性和只执行一次的特性。

sync.Once 是一个非常简单的类型,它只有一个 Do 方法,下面是 sync.Once 的内部实现代码:

 type Once struct {
     m    Mutex
     done uint32
 }
 ​
 func (o *Once) Do(f func()) {
     if atomic.LoadUint32(&o.done) == 1 {
         return
     }
 ​
     o.m.Lock()
     defer o.m.Unlock()
     if o.done == 0 {
         defer atomic.StoreUint32(&o.done, 1)
         f()
     }
 }

从上面的代码可以看出,sync.Once 的实现非常简单。在 Do 方法中,它首先检查 done 字段是否为 1,如果是,则直接返回,否则就获取锁。获取锁之后,它再次检查 done 字段是否为 0,如果是,则执行传入的函数 f,并将 done 字段设置为 1。由于只有一个 goroutine 能够获取到锁并执行 f,所以 sync.Once 可以保证 f 只会被执行一次。

需要注意的是,sync.Once 的实现中使用了 defer 关键字,这是为了保证在函数返回时能够释放锁,并将 done 字段设置为 1。这种写法非常巧妙,能够避免很多常见的并发问题,比如死锁、竞争条件等。

2. sync.Once 的错误处理

由于 sync.Once 能够确保某个函数只会执行一次,因此在函数执行失败时,我们需要考虑如何处理错误。

一种常见的错误处理方式是将错误信息存储在 sync.Once 结构体中,并在后续的调用中返回错误信息。下面是一个示例:

 package main
 ​
 import (
     "errors"
     "fmt"
     "sync"
 )
 ​
 type Config struct {
     Name string
 }
 ​
 var (
     config     *Config
     configOnce sync.Once
     configErr  error
 )
 ​
 func loadConfig() error {
     // 模拟配置加载失败
     return errors.New("failed to load config")
 }
 ​
 func getConfig() (*Config, error) {
     configOnce.Do(func() {
         // 只有在第一次执行时才会调用 loadConfig 函数
         if err := loadConfig(); err != nil {
             configErr = err
         } else {
             config = &Config{Name: "example"}
         }
     })
 ​
     return config, configErr
 }
 ​
 func main() {
     cfg, err := getConfig()
     if err != nil {
         fmt.Printf("error: %v\n", err)
         return
     }
 ​
     fmt.Printf("config: %+v\n", cfg)
 }

在这个示例中,我们使用 sync.Once 来确保 getConfig() 函数只会执行一次。在第一次执行时,我们通过 loadConfig() 函数加载配置,如果加载失败,我们将错误信息存储在 configErr 变量中,否则将配置信息存储在 config 变量中。在后续的调用中,我们将 config 和 configErr 一起返回,这样就能够正确地处理函数执行失败的情况了。

3. sync.Once 的嵌套调用

在某些情况下,我们可能需要在 sync.Once 中嵌套调用其他函数,以实现更复杂的逻辑。这时候我们需要注意的是,在嵌套调用中,我们需要使用新的 sync.Once 实例来保证内部函数的执行只会发生一次。下面是一个示例:

 package main
 ​
 import (
     "fmt"
     "sync"
 )
 ​
 func main() {
     var once sync.Once
 ​
     // 外层函数
     outer := func() {
         fmt.Println("outer")
         // 内层函数
         inner := func() {
             fmt.Println("inner")
         }
 ​
         var innerOnce sync.Once
         innerOnce.Do(inner)
     }
 ​
     // 外层函数只会执行一次
     once.Do(outer)
     once.Do(outer)
 }

在这个示例中,我们定义了一个外层函数 outer 和一个内层函数 inner,然后在 outer 函数中使用了一个新的 sync.Once 实例 innerOnce 来保证 inner 函数只会执行一次。在最后的调用中,我们使用一个新的 sync.Once 实例 once 来保证 outer 函数只会执行一次,避免了重复执行造成的问题。

4. 并发性能

并发编程中,性能是一个非常重要的指标。因此,我们需要了解 sync.Once 在并发场景下的性能表现,以便在实际应用中选择合适的并发控制方案。

sync.Once 的性能表现在很大程度上取决于被保护函数的实际执行时间。如果被保护函数执行时间很长,那么 sync.Once 的性能表现会受到影响,因为每个 goroutine 都需要等待被保护函数的执行结束才能继续执行。

下面是一个简单的性能测试示例,用于比较 sync.Once 和传统的锁机制在并发场景下的性能表现:

 package main
 ​
 import (
     "sync"
     "sync/atomic"
     "time"
 )
 ​
 const (
     numGoroutines = 1000
     numRepeats    = 100
 )
 ​
 func testWithSyncOnce() {
     var once sync.Once
 ​
     for i := 0; i < numGoroutines; i++ {
         go func() {
             for j := 0; j < numRepeats; j++ {
                 once.Do(func() {
                     time.Sleep(10 * time.Millisecond)
                 })
             }
         }()
     }
 }
 ​
 func testWithMutex() {
     var mutex sync.Mutex
     var done int64
 ​
     for i := 0; i < numGoroutines; i++ {
         go func() {
             for j := 0; j < numRepeats; j++ {
                 mutex.Lock()
                 if done == 0 {
                     time.Sleep(10 * time.Millisecond)
                     atomic.StoreInt64(&done, 1)
                 }
                 mutex.Unlock()
             }
         }()
     }
 }
 ​
 func main() {
     start := time.Now()
     testWithSyncOnce()
     fmt.Printf("sync.Once: %v\n", time.Since(start))
 ​
     start = time.Now()
     testWithMutex()
     fmt.Printf("Mutex: %v\n", time.Since(start))
 }

在这个示例中,我们定义了两个函数 testWithSyncOnce 和 testWithMutex,分别使用 sync.Once 和传统的锁机制来实现并发控制。在每个函数中,我们使用 numGoroutines 个 goroutine 来执行被保护函数,并重复执行 numRepeats 次。

在 main 函数中,我们使用 time 包来测量两个函数的执行时间,并比较它们的性能表现。

实际上,由于 sync.Once 内部使用原子操作来控制执行状态,因此在被保护函数执行时间很短的情况下,sync.Once 的性能表现要优于传统的锁机制。但是,在被保护函数执行时间较长的情况下,sync.Once 的性能表现会逐渐变差。

在实际应用中,我们需要根据被保护函数的实际执行时间和并发访问量来选择合适的并发控制方案。

5. 总结

在本文中,我们介绍了 sync.Once 的基本用法,并讨论了它在错误处理、嵌套调用和并发性能方面的注意事项。在实际应用中,sync.Once 是一个非常实用的并发控制工具,它可以保证某个函数只会执行一次,并提高程序的性能表现。

当然,除了 sync.Once,Golang 还提供了其他的并发控制工具,比如 sync.WaitGroup、sync.Mutex 等。在实际应用中,我们需要根据具体的场景来选择合适的并发控制方案。

如果被保护函数的执行时间很短,且并发访问量较高,那么可以考虑使用 sync.Once。如果被保护函数的执行时间较长,或者需要多个 goroutine 共同协作完成某个任务,那么可以考虑使用 sync.WaitGroup。如果需要保证某些资源在同一时刻只能被一个 goroutine 访问,那么可以考虑使用 sync.Mutex。

在实际应用中,我们需要根据具体的场景来选择合适的并发控制方案,并结合实际性能测试数据来进行优化。只有合理使用并发控制工具,才能充分发挥 Golang 的并发编程优势,提高程序的性能和稳定性。

以上就是Golang sync.Once实现单例模式的方法详解的详细内容,更多关于Golang sync.Once的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Golang sync.Once实现单例模式的方法详解

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

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

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Golang sync.Once实现单例模式的方法详解
    目录1. sync.Once 的原理和实现2. sync.Once 的错误处理3. sync.Once 的嵌套调用4. 并发性能5. 总结Go 语言的 sync 包提供了一系列同步原...
    99+
    2023-05-18
    Golang sync.Once实现单例模式 Golang sync.Once原理 Golang sync.Once使用 Golang sync.Once
  • go sync.Once实现高效单例模式详解
    目录1. 简介2. 基本实现2.1 单例模式定义2.2 sync.Once实现单例模式2.3 其他方式实现单例模式2.3.1 全局变量定义时赋值,实现单例模式2.3.2 init 函...
    99+
    2023-03-14
    go sync.Once单例模式 go sync.Once
  • go sync.Once如何实现高效单例模式
    这篇文章主要讲解了“go sync.Once如何实现高效单例模式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go sync.Once如何实现高效单例模式”吧!基本实现1...
    99+
    2023-07-05
  • java设计模式-单例模式实现方法详解
    目录饿汉式静态变量静态代码块懒汉式线程不安全线程安全双重检查静态内部类总结单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要...
    99+
    2022-11-12
  • C++单例模式的几种实现方法详解
    目录局部静态变量方式静态成员变量指针方式智能指针方式辅助类智能指针单例模式通用的单例模板类总结局部静态变量方式 //通过静态成员变量实现单例 //懒汉式 class Single2 ...
    99+
    2022-11-13
  • Golang基于sync.Once实现单例的操作代码
    目录基于sync.Once实现单例单例类型定义Driver类Field connonce.Do(func() {})并发访问once.Do()对外暴露方法Conn()重新new(Dr...
    99+
    2022-11-11
  • Python实现单例模式的四种方式详解
    简介:单例模式可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。适用性于当类只能有一个实例而且客户可以从一个众所周知的访问点访问它,例如访问数据库、MQ等。 实现方式: 1、...
    99+
    2022-11-11
  • Java单例模式的6种实现方式详解
    目录为什么使用单例模式使用单例模式需要注意的关键点单例模式的几种写法1. 饿汉式2. 懒汉式3. DCL(Double CheckLock)实现单例4. 静态内部类5...
    99+
    2022-11-12
  • Java之单例模式实现方案详解
      单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式。但是除了这两种方式,本文还会介绍其他...
    99+
    2022-11-12
  • Golang设计模式之单例模式详细讲解
    目录单例模式概念示例单例模式 单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但...
    99+
    2023-01-11
    Go单例模式 Go设计模式
  • golang单例模式怎么实现
    Go语言中的单例模式可以通过以下几种方式来实现: 使用全局变量:定义一个全局变量,在需要使用单例对象的地方直接使用该全局变量。在包...
    99+
    2023-10-21
    golang
  • Android 实现夜间模式的快速简单方法实例详解
    ChangeMode 项目地址:ChangeMode Implementation of night mode for Android. 用最简单的方式实现夜间模式,支持Lis...
    99+
    2022-06-06
    方法 Android
  • C++实现单例模式的方法
    目录饿汉模式懒汉模式锁 + 智能指针局部静态变量总结 饿汉模式 类实例化就会占用内存,浪费资源,效率高,不存在线程安全问题。 class Singleton{ Singl...
    99+
    2022-11-12
  • java 单例模式的实例详解
    java 单例模式的实例详解概念:    java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。    单例模式有一下特点:   1、单例类只能有一个实例。   2、单例类必须自己自己创建自己的唯一...
    99+
    2023-05-31
    java 单例模式 ava
  • Python单例模式实例详解
    本文实例讲述了Python单例模式。分享给大家供大家参考,具体如下: 单例模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点。 实现某个类只有一个实例的途径: 1,让一个全局变量使得一个对象被访问,...
    99+
    2022-06-04
    详解 实例 模式
  • Python中单例模式的实现方法
    单例 — 让 类 创建的对象,在系统中 只有唯一的一个实例; 1)、定义一个类属性,初始值是 None ,用于记录 单例对象的引用;2)、重写 new 方法;3)、如果 ...
    99+
    2022-11-11
  • Golang设计模式工厂模式实战写法示例详解
    目录拆出主板工厂模式流程代码实战抽象能力,定义接口实现工厂,支持注册和获取实现主流程只依赖接口完成扩展 => 适配器,实现接口注册适配器到工厂里小结拆出主板 今天带大家看一下怎...
    99+
    2022-11-11
  • java 单例模式和工厂模式实例详解
    单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例。私有的构造方法指向自己实例的私有静态引用以自己实例为返回值的静态的公有的方法饿汉式单例 public class Singleton { private ...
    99+
    2023-05-31
    java 单例模式 工厂模式
  • Python实现单例模式的5种方法
    目录基本介绍优缺点Python实现方式1,元类实现:方式2,继承实现:方式3,装饰器实现:方式4,模块实现:方式5,@classmethod实现单例模式:基本介绍 一个对象只允许被一次创建,一个类只能创建一个对象,...
    99+
    2022-06-02
    Python 单例模式
  • kotlinobject关键字单例模式实现示例详解
    目录正文一、 匿名内部类二、单例模式三、伴生对象1、深入分析伴生对象2、用伴生对象实现工厂模式3、用伴生对象实现单例模式(1)、借助懒加载委托(2)、伴生对象 Double Chec...
    99+
    2023-01-12
    kotlin object关键字单例模式 kotlin object
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作