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 相关文章推荐
win10下go mod配置方式
Apr 25 Golang
golang 如何通过反射创建新对象
Apr 28 Golang
goland设置颜色和字体的操作
May 05 Golang
go设置多个GOPATH的方式
May 05 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
go 实现简易端口扫描的示例
May 22 Golang
golang生成并解析JSON
Apr 14 Golang
golang用type-switch判断interface的实际存储类型
Apr 14 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
Golang 入门 之url 包
May 04 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 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
Smarty模板快速入门
2007/01/04 PHP
php中将时间差转换为字符串提示的实现代码
2011/08/08 PHP
thinkphp视图模型查询提示ERR: 1146:Table 'db.pr_order_view' doesn't exist的解决方法
2014/10/30 PHP
老生常谈PHP面向对象之解释器模式
2017/05/17 PHP
用js实现计算代码行数的简单方法附代码
2007/08/13 Javascript
jQuery AnythingSlider滑动效果插件
2010/02/07 Javascript
JS跨域代码片段
2012/08/30 Javascript
jQuery基本选择器选择元素使用介绍
2013/04/18 Javascript
javascript中innerText和innerHTML属性用法实例分析
2015/05/13 Javascript
详解照片瀑布流效果(js,jquery分别实现与知识点总结)
2017/01/01 Javascript
JavaScript与JQUERY获取元素的宽、高和位置
2017/02/26 Javascript
weex slider实现滑动底部导航功能
2017/08/28 Javascript
postman+json+springmvc测试批量添加实例
2018/03/31 Javascript
从Vuex中取出数组赋值给新的数组,新数组push时报错的解决方法
2018/09/18 Javascript
vue.js实现会动的简历(包含底部导航功能,编辑功能)
2019/04/08 Javascript
layui表格内放置图片,并点击放大的实例
2019/09/10 Javascript
javascript实现切割轮播效果
2019/11/28 Javascript
浅析Vue 中的 render 函数
2020/02/28 Javascript
Python中的列表知识点汇总
2015/04/14 Python
Django 生成登陆验证码代码分享
2017/12/12 Python
Python中pillow知识点学习
2018/04/30 Python
python3实现163邮箱SMTP发送邮件
2018/05/22 Python
django 框架实现的用户注册、登录、退出功能示例
2019/11/28 Python
python集合删除多种方法详解
2020/02/10 Python
Biblibili视频投稿接口分析并以Python实现自动投稿功能
2021/02/05 Python
利用CSS3动画实现圆圈由小变大向外扩散的效果实例
2018/09/10 HTML / CSS
利用HTML5 Canvas制作键盘及鼠标动画的实例分享
2016/03/15 HTML / CSS
canvas实现图片镜像翻转的2种方式
2020/07/22 HTML / CSS
澳大利亚足球鞋和服装购物网站:Ultra Football
2018/10/11 全球购物
为什么要有struct关键字
2012/05/08 面试题
什么是托管函数?托管函数有什么用?
2014/06/15 面试题
英文求职信写作小建议
2014/02/16 职场文书
《孔繁森》教学反思
2014/04/17 职场文书
办公室个人总结
2015/02/28 职场文书
小学教师自我评价
2015/03/04 职场文书
护士业务学习心得体会
2016/01/25 职场文书