文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-07-06 00:45

关注

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

sync.Once 基本概念

什么是 sync.Once

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

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 的值的判断。

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

加强的 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怎么使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯