Go语言并发编程 sync.Once


Posted in Golang onOctober 16, 2021

sync.Once用于保证某个动作只被执行一次,可用于单例模式中,比如初始化配置。我们知道init()函数也只会执行一次,不过它是在main()函数之前执行,如果想要在代码执行过程中只运行某个动作一次,可以使用sync.Once,下面来介绍一下它的使用方法。

先来看下面的代码:

package main

import (
 "fmt"
 "sync"
)


func main() {
 var num = 6
 var once sync.Once

 add_one := func() {
  num = num + 1
 }

 minus_one := func() {
  num = num - 1
 } 

 once.Do(add_one)
 fmt.Printf("The num: %d\n", num)
 once.Do(minus_one)
 fmt.Printf("The num: %d\n", num)
}

执行结果:

The num: 7
The num: 7

sync.Once类型提供了一个Do方法,Do方法只接受一个参数,且参数类型必须是func() ,也就是没有参数声明和结果声明的函数。

Do方法只会执行首次被调用时传入的那个函数,只执行一次,也不会执行其它函数。上面的例子中,即使传入的函数不同,也只会执行第一次传入的那个函数。如果有多个只执行一次的函数,需要为每一个函数分配一个sync.Once类型的值:

func main() {
 var num = 6
 var once1 sync.Once
 var once2 sync.Once

 add_one := func() {
  num = num + 1
 }

 minus_one := func() {
  num = num - 1
 } 

 once1.Do(add_one)
 fmt.Printf("The num: %d\n", num)
 once2.Do(minus_one)
 fmt.Printf("The num: %d\n", num)
}

sync.Once类型是一个结构体类型,一个是名为doneuint32类型字段,还有一个互斥锁m

type Once struct {
 done uint32
 m    Mutex
}

done字段的值只可能是0或者1,Do方法首次调用完成后,done的值就变为了1。done的值使用四个字节的uint32类型的原因是为了保证对它的操作是“原子操作”,通过调用atomic.LoadUint32函数获取它的值,如果为1,直接返回,不会执行函数。

如果为0,Do方法会立即锁定字段m,如果这里不加锁,多个goroutine 同时执行到Do方法时判断都为0,则都会执行函数,所以Once是并发安全的。

加锁之后,会再次检查done字段的值,如果满足条件,执行传入的函数,并用原子操作函数atomic.StoreUint32done的值设置为1。

下面是Once的源码:

func (o *Once) Do(f func()) {

 if atomic.LoadUint32(&o.done) == 0 {
  o.doSlow(f)
 }
}

func (o *Once) doSlow(f func()) {
 o.m.Lock()
 defer o.m.Unlock()
 if o.done == 0 {
  defer atomic.StoreUint32(&o.done, 1)
  f()
 }
}

源码非常简洁,和GoF 设计模式中的单例模式非常相似。

到此这篇关于Go语言并发编程 sync.Once的文章就介绍到这了,更多相关Go语言 sync.Once内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
为什么不建议在go项目中使用init()
Apr 12 Golang
基于go interface{}==nil 的几种坑及原理分析
Apr 24 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
对Golang中的FORM相关字段理解
May 02 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
Go 语言结构实例分析
Jul 04 Golang
入门学习Go的基本语法
Jul 07 Golang
浅谈GO中的Channel以及死锁的造成
Mar 18 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
Golang 链表的学习和使用
Apr 19 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
Go 通过结构struct实现接口interface的问题
Oct 05 #Golang
golang实现一个简单的websocket聊天室功能
深入理解go slice结构
Sep 15 #Golang
Golang表示枚举类型的详细讲解
golang 语言中错误处理机制
Aug 30 #Golang
Golang并发操作中常见的读写锁详析
Aug 30 #Golang
Go中的条件语句Switch示例详解
Aug 23 #Golang
You might like
php异常:Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE  eval()'d code error
2011/05/19 PHP
PHP学习笔记 IIS7下安装配置php环境
2012/10/29 PHP
php使用ftp实现文件上传与下载功能
2017/07/21 PHP
PHP Socket网络操作类定义与用法示例
2017/08/30 PHP
PHP中递归的实现实例详解
2017/11/14 PHP
javascript之函数直接量(function(){})()
2007/06/29 Javascript
JScript分割字符串示例代码
2013/09/04 Javascript
在父页面调用子页面的JS方法
2013/09/29 Javascript
用Jquery选择器计算table中的某一列某一行的合计
2014/08/13 Javascript
JS+DIV+CSS实现的经典标签切换效果代码
2015/09/14 Javascript
探讨JavaScript标签位置的存放与功能有无关系
2016/01/15 Javascript
jquery对象和DOM对象的任意相互转换
2016/02/21 Javascript
Node.js常用工具之util模块
2017/03/09 Javascript
前端开发之CSS原理详解
2017/03/11 Javascript
详解vue-router2.0动态路由获取参数
2017/06/14 Javascript
React Native之prop-types进行属性确认详解
2017/12/19 Javascript
Vue中的无限加载vue-infinite-loading的方法
2018/04/08 Javascript
jQuery实现鼠标移入移出事件切换功能示例
2018/09/06 jQuery
Vuejs通过拖动改变元素宽度实现自适应
2020/09/02 Javascript
微信小程序对图片进行canvas压缩的方法示例详解
2020/11/12 Javascript
[01:14]英雄,所敬略同——2018完美盛典宣传视频
2018/12/05 DOTA
Python3.x中自定义比较函数
2015/04/24 Python
开始着手第一个Django项目
2015/07/15 Python
Python中规范定义命名空间的一些建议
2016/06/04 Python
Python中利用LSTM模型进行时间序列预测分析的实现
2019/07/26 Python
使用PyCharm安装pytest及requests的问题
2020/07/31 Python
中粮集团旗下食品网上购物网站:我买网
2016/09/22 全球购物
GWT都有什么特性
2016/12/02 面试题
我的求职计划书
2014/01/10 职场文书
心理健康活动总结
2014/04/30 职场文书
卫生院健康教育实施方案
2014/06/07 职场文书
护士2014年终工作总结
2014/11/11 职场文书
2014年科室工作总结
2014/11/20 职场文书
2015年中个人总结范文
2015/03/10 职场文书
特种设备安全管理制度
2015/08/06 职场文书
导游词之绍兴柯岩古镇
2020/01/09 职场文书