前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang语言标准库 sync 包的 Once 怎么使用?

Golang语言标准库 sync 包的 Once 怎么使用?

作者头像
frank.
发布2020-12-08 14:53:23
2K0
发布2020-12-08 14:53:23
举报

01

介绍

在 Go 语言中,sync 包有一个 Once 类型,官方文档介绍 Once 是一个只执行一次操作的对象。所以,Once 一般用于并发执行,但只需初始化一次的共享资源。

02

基本用法

Once 的使用也非常简单,Once 只有一个 Do 方法,接收一个无参数无返回值的函数类型的参数 f,不管调用多少次 Do 方法,参数 f 只在第一次调用 Do 方法时执行。

代码示例:

通过阅读示例代码,可以发现代码中定义了两个函数类型的变量 func1 和 func2,分别作为参数传递给两次调用的 Do 方法,执行代码,结果只打印第一次调用 Do 方法传入的 func1 参数的值。

03

实现原理

sync.Once 源码:

代码语言:javascript
复制
type Once struct {
  done uint32
  m    Mutex
}

func (o *Once) Do(f func()) {
  if atomic.LoadUint32(&o.done) == 0 { 
    // 原子获取 done 的值,判断 done 的值是否为 0,如果为 0 就调用 doSlow 方法,进行二次检查。
    o.doSlow(f)
  }
}

func (o *Once) doSlow(f func()) {
  // 二次检查时,持有互斥锁,保证只有一个 goroutine 执行。
  o.m.Lock()
  defer o.m.Unlock()
  if o.done == 0 {
    // 二次检查,如果 done 的值仍为 0,则认为是第一次执行,执行参数 f,并将 done 的值设置为 1。
    defer atomic.StoreUint32(&o.done, 1)
    f()
  }
}

通过阅读 sync.Once 的源码,可以发现 Once 结构体中包含两个字段,分别是 uint32 类型的 done 和 Mutex 类型的 m。并且 Once 实现了两个方法,分别是 Do 和 doSlow。其中 doSlow 是一个非可导出方法,只能被 Do 方法调用。

Done 方法先原子获取 done 的值,如果 done 的值为 0,则调用 doSlow 方法进行二次检查,二次检查时,持有互斥锁,保证只有一个 goroutine 执行操作,二次检查结果仍为 0,则认为是第一次执行,程序执行函数类型的参数 f,然后将 done 的值设置为 1。

04

踩坑

我们已经介绍过 Once 是一个只执行一次操作的对象,假如我们在 Do 方法中再次调用 Do 方法会怎么样呢?代码如下:

阅读代码,我们定义了两个函数类型的变量 func1 和 func2,其中 func1 的函数体内,调用 Do 方法并将 func2 作为参数传递给它,最后调用给定 func1 作为参数的 Do 方法,运行结果是导致程序死锁。所以,记住不要在Do 方法的给定参数中,调用 Do 方法,否则会产生死锁。

05

总结

本文开篇介绍了 Once 的官方定义和使用场景,然后结合示例代码,介绍了 Once 的基本使用,并通过阅读源码,介绍了 Once 的实现原理,最后列举了一个容易踩的「坑」。

参考资料:

https://golang.org/pkg/sync/#Once

推荐阅读:

Go 语言使用标准库 sync 包的 mutex 互斥锁解决数据竞态

Golang 语言标准库 sync 包的 RWMutex 读写互斥锁怎么使用?

Golang语言标准库 sync 包的 WaitGroup 怎么使用?

Golang语言标准库 sync 包的 Cond 怎么使用?

Go语言学习之并发

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go语言开发栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档