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各时间字符串使用解析
Apr 02 Golang
为什么不建议在go项目中使用init()
Apr 12 Golang
goland 恢复已更改文件的操作
Apr 28 Golang
基于Go Int转string几种方式性能测试
Apr 28 Golang
golang日志包logger的用法详解
May 05 Golang
Golang 编译成DLL文件的操作
May 06 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
go xorm框架的使用
May 22 Golang
golang 语言中错误处理机制
Aug 30 Golang
Go并发4种方法简明讲解
Apr 06 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
golang生成并解析JSON
Apr 14 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
经典的星际争霸,满是回忆的BGM
2020/04/09 星际争霸
PHP中array_merge和array相加的区别分析
2013/06/17 PHP
WordPres对前端页面调试时的两个PHP函数使用小技巧
2015/12/22 PHP
PHP+apc+ajax实现的ajax_upload上传进度条代码
2016/01/25 PHP
在PHP语言中使用JSON和将json还原成数组的方法
2016/07/19 PHP
javaScript 利用闭包模拟对象的私有属性
2011/12/29 Javascript
JS获得URL超链接的参数值实例代码
2013/06/21 Javascript
jquery批量控制form禁用的代码
2013/08/06 Javascript
jQuery事件之键盘事件(ctrl+Enter回车键提交表单等)
2014/05/11 Javascript
jquery网页回到顶部效果(图标渐隐,自写)
2014/06/16 Javascript
js中各种类型的变量在if条件中是true还是false
2014/07/16 Javascript
推荐10 款 SVG 动画的 JavaScript 库
2015/03/24 Javascript
在JavaScript中操作时间之getUTCDate()方法的使用
2015/06/10 Javascript
AngularJS创建自定义指令的方法详解
2016/11/03 Javascript
javascript中神奇的 Date对象小结
2017/10/12 Javascript
gulp安装以及打包合并的方法教程
2017/11/19 Javascript
解决vue-cli创建项目的loader问题
2018/03/13 Javascript
vue组件详解之使用slot分发内容
2018/04/09 Javascript
微信小程序实现跑马灯效果完整代码(附效果图)
2018/05/30 Javascript
react项目实践之webpack-dev-serve
2018/09/14 Javascript
jQuery实现动态添加和删除input框代码实例
2019/03/29 jQuery
js实现的格式化数字和金额功能简单示例
2019/07/30 Javascript
WEB前端性能优化的7大手段详解
2020/02/04 Javascript
vue中el-input绑定键盘按键(按键修饰符)
2020/07/22 Javascript
Python实现远程调用MetaSploit的方法
2014/08/22 Python
在Python中使用HTMLParser解析HTML的教程
2015/04/29 Python
python遍历数组的方法小结
2015/04/30 Python
Python抓取聚划算商品分析页面获取商品信息并以XML格式保存到本地
2018/02/23 Python
使用Python正则表达式操作文本数据的方法
2019/05/14 Python
基于Python3.6中的OpenCV实现图片色彩空间的转换
2020/02/03 Python
Python+unittest+requests+excel实现接口自动化测试框架
2020/12/23 Python
企业管理毕业生求职信范文
2014/03/07 职场文书
小学同学聚会感言
2015/07/30 职场文书
2016年大学迎新晚会工作总结
2015/10/15 职场文书
学习委员竞选稿
2015/11/20 职场文书
python实现ROA算子边缘检测算法
2021/04/05 Python