乐趣区

关于后端:深度剖析Golang-syncOnce源码

点击上方蓝色“迈莫 coding”,抉择“设为星标”

目录

 

=====================================

  • 什么是 sync.Once
  • 如何应用 sync.Once
  • 源码剖析

什么是 sync.Once


===========================================

Once 能够用来执行且仅仅执行一次动作,经常用于单例对象的初始化场景。

Once 经常用来初始化单例资源,或者并发拜访只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源。

sync.Once 只裸露了一个办法 Do,你能够屡次调用 Do 办法,然而只有第一次调用 Do 办法时 f 参数才会执行,这里的 f 是一个无参数无返回值的函数。

如何应用 sync.Once


=============================================

就拿我当初负责的一个我的项目来说,因为我的项目的配置是挂在第三方平台上,所以在我的项目启动时须要获取资源配置,因为须要一个办法来保障配置仅此只获取一次,因而,咱们思考应用 sync.Once 来获取资源。这样的话,能够避免在其余中央调用获取资源办法,该办法仅此执行一次。

上面我简略写个 Demo 来演示一个 sync.Once 如何应用

package main
import (
   "fmt"
   "sync"
)
var once sync.Once
var con string
func main() {once.Do(func() {con = "hello Test once.Do"})
   fmt.Println(con)
}

代码阐明:

代码的话比较简单,就是通过调用 Do 办法,采纳闭包形式,将字符串 (“hello Test once.Do”) 赋值给 con,进而打印出值,这就是 sync.Once 的应用,比拟容易上手。

但咱们用一个办法或者框架时,如果不对其一目了然,总有点不太靠谱,感觉心里不虚浮。为此,咱们来聊一聊 sync.Once 的源码实现,让他无处可遁。

源码剖析


===========================

接下来剖析 sync.Do 到底是如何实现的,它存储在包 sync 下 once.go 文件中,源代码如下:

// sync/once.go


type Once struct {
   done uint32 // 初始值为 0 示意还未执行过,1 示意曾经执行过
   m    Mutex 
}
func (o \*Once) Do(f func()) {// 判断 done 是否为 0,若为 0,示意未执行过,调用 doSlow()办法初始化
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}

// 加载资源
func (o \*Once) doSlow(f func()) {o.m.Lock()
   defer o.m.Unlock()
   // 采纳双重检测机制 加锁判断 done 是否为零
   if o.done == 0 {// 执行完 f()函数后,将 done 值设置为 1
      defer atomic.StoreUint32(&o.done, 1)
      // 执行传入的 f()函数
      f()}
}

接下来会分为两大部分进行剖析,第一部分为 Once 的构造体组成构造,第二局部为 Do 函数的实现原理,我会在代码上加上正文,保障用心浏览完都有播种。

构造体


type Once struct {
   done uint32 // 初始值为 0 示意还未执行过,1 示意曾经执行过
   m    Mutex 
}

首先定义一个 struct 构造体 Once,外面存储两个成员变量,别离为 donem

done 成员变量

  • 1 示意资源未初始化,须要进一步初始化
  • 0 示意资源已初始化,无需初始化,间接返回即可

m 成员变量

  • 为了避免多个 goroutine 调用 doSlow() 初始化资源时,造成资源屡次初始化,因而采纳 Mutex 锁机制来保障有且仅初始化一次

Do


func (o \*Once) Do(f func()) {// 判断 done 是否为 0,若为 0,示意未执行过,调用 doSlow()办法初始化
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}


// 加载资源
func (o \*Once) doSlow(f func()) {o.m.Lock()
   defer o.m.Unlock()
   // 采纳双重检测机制 加锁判断 done 是否为零
   if o.done == 0 {// 执行完 f()函数后,将 done 值设置为 1
      defer atomic.StoreUint32(&o.done, 1)
      // 执行传入的 f()函数
      f()}
}

调用 Do 函数时,首先判断 done 值是否为 0,若为 1,示意传入的匿名函数 f() 已执行过,无需再次执行;若为 0,示意传入的匿名函数 f() 还未执行过,则调用 doSlow() 函数进行初始化。

doSlow() 函数中,若并发的 goroutine 进入该函数中,为了保障仅有一个 goroutine 执行 f() 匿名函数。为此,须要加互斥锁保障只有一个 goroutine 进行初始化,同时采纳了 双查看的机制(double-checking),再次判断 o.done 是否为 0,如果为 0,则是第一次执行,执行结束后,就将 o.done 设置为 1,而后开释锁。

即便此时有多个 goroutine 同时进入了 doSlow 办法,因为双查看的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f。

这样既保证了并发的 goroutine 会期待 f 实现,而且还不会屡次执行 f。

分割线

往期举荐

[

【七天从零实现 ORM 框架】Day01: 序言

](http://mp.weixin.qq.com/s?__b…

[

go 反射那些事儿 |isNil()/isValid()/Call()函数应用

](http://mp.weixin.qq.com/s?__b…

[

go 那些事儿 |go 反射第一弹(TypeOf)

](http://mp.weixin.qq.com/s?__b…

[

go 那些事儿 |channel 应用及其实现原理

](http://mp.weixin.qq.com/s?__b…

go 那些事儿 |defer 必把握常识

文章也会继续更新,能够微信搜寻「迈莫 coding」第一工夫浏览。每天分享优质文章、大厂教训、大厂面经,助力面试,是每个程序员值得关注的平台。

  1. 你点的每个赞,我都认真当成了喜爱

退出移动版