深入源码分析golang之sync.Once

君荣
• 阅读 1261

深入源码分析golang之sync.Once

什么是sync.Once

  官方文档对它的描述是:一个对象将完全执行一次,不得复制。常常用来单例对象的初始化场景,或者并发访问只需要初始化一次的共享资源。sync.Once只暴露了一个方法Do,可以多次调用,但是只有第一次调用Do方法时f参数才会执行,这里的f是一个无参数无返回值的函数。下面来看下官方给出来的demo:

 `1package main`
 `2`
 `3import (`
 `4    "fmt"`
 `5    "sync"`
 `6)`
 `7`
 `8func main() {`
 `9    var once sync.Once`
`10    onceBody := func() {`
`11        fmt.Println("Only once")`
`12    }`
`13    done := make(chan bool)`
`14    for i := 0; i < 10; i++ {`
`15        go func() {`
`16            once.Do(onceBody)`
`17            done <- true`
`18        }()`
`19    }`
`20    for i := 0; i < 10; i++ {`
`21        <-done`
`22    }`
`23}`
`24// 结果只打印一次:only once`

深入源码分析golang之sync.Once

源码分析

sync.Once的源代码还很少的,直接在代码里面作分析

 `1package sync`
 `2`
 `3import (`
 `4    "sync/atomic"`
 `5)`
 `6`
 `7type Once struct {`
 `8    done uint32 // 初始值为0表示还未执行过,1表示已经执行过`
 `9    m    Mutex  // m为互斥锁`
`10}`
`11`
`12func (o *Once) Do(f func()) {`
`13    // 判断done是否为0.若为0,表示未执行过,调用doSlow方法`
`14    if atomic.LoadUint32(&o.done) == 0 {`
`15        o.doSlow(f)`
`16    }`
`17}`
`18`
`19func (o *Once) doSlow(f func()) {`
`20    // 互斥锁加锁解锁`
`21    o.m.Lock()`
`22    defer o.m.Unlock()`
`23    // 加锁判断done是否为0`
`24    if o.done == 0 {`
`25        // 执行完f()函数后,将done值设置为1`
`26        defer atomic.StoreUint32(&o.done, 1)`
`27        f()`
`28    }`
`29}`

Do方法流程图:

深入源码分析golang之sync.Once

疑问1:为什么在do方法的时候没有用到cas原子判断?

在do方法源码注释中的时候有这么一段话,说如果是使用 atomic.CompareAndSwapUint32(&o.done, 0, 1)此方法不会保证实现f函数调用完成。为什么会这么说,同时进行两次调用,cas的获胜者将调用f函数,失败者将立即返回,而无需等待第一个对f的调用完成。此时f函数还在执行过程中,你就已经回复了once.Do成功了,此时全局变量还没有创建出来,行为是无法定义的。那么怎么解决呢?有以下两个思路:

1)热路径:用原子读done的值,保证竞态条件正确;

2)加锁:既然不能用cas原子操作,那就用加锁的方式来保证原子性,如果done==0,那么走慢路径,先加锁,然后在执行f函数,最后将done设置为1,并解锁。

重点千万不要把同一sync.Once用在嵌套结构中,这样非常容易形成死锁!

`1func once() {`
`2    once := &sync.Once{}`
`3    once.Do(func() {`
`4        once.Do(func() {`
`5            fmt.Println("test nestedDo")`
`6        })`
`7    })`
`8    //输出:fatal error: all goroutines are asleep - deadlock!`
`9}`

深入源码分析golang之sync.Once

sync.Once实现单例模式

单例模式可以说是设计模式里面最简单的了,Go可以借助sync.Once来实现,代码如下:

 `1package main`
 `2`
 `3import (`
 `4    "fmt"`
 `5    "sync"`
 `6)`
 `7`
 `8type Singleton struct{}`
 `9`
`10var (`
`11    singleton *Singleton`
`12    once      sync.Once`
`13)`
`14`
`15func GetSingleton() *Singleton {`
`16    once.Do(func() {`
`17        fmt.Println("Create Obj")`
`18        singleton = new(Singleton)`
`19    })`
`20    return singleton`
`21}`
`22`
`23func main() {`
`24    var wg sync.WaitGroup`
`25    for i := 0; i < 5; i++ {`
`26        wg.Add(1)`
`27        go func() {`
`28            obj := GetSingleton()`
`29            fmt.Printf("%p\n", obj)`
`30            wg.Done()`
`31        }()`
`32    }`
`33    wg.Wait()`
`34    // 只打印一次 Create Obj`
`35}`

扫码关注

获取更多干货内容

深入源码分析golang之sync.Once

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
4年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
4年前
mysql查询每个学生的各科成绩,以及总分和平均分
今天看一个mysql教程,看到一个例子,感觉里面的解决方案不是很合理。问题如下:有学生表:!在这里插入图片描述(https://oscimg.oschina.net/oscnet/07b001b0c6cb7e0038a9299e768fc00a0d3.png)成绩表:!在这里插入图片描述(https://oscimg.o
君荣
君荣
Lv1
只谈乾坤风月,莫论人间是非。
文章
4
粉丝
0
获赞
0