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语言map与string的相互转换的实现
Apr 07 Golang
为什么不建议在go项目中使用init()
Apr 12 Golang
golang中实现给gif、png、jpeg图片添加文字水印
Apr 26 Golang
golang中的空slice案例
Apr 27 Golang
Go使用协程交替打印字符
Apr 29 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
goland设置颜色和字体的操作
May 05 Golang
Golang二维数组的使用方式
May 28 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
基于Go语言构建RESTful API服务
Jul 25 Golang
golang操作rocketmq的示例代码
Apr 06 Golang
Golang流模式之grpc的四种数据流
Apr 13 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
晶体管单管来复再生式收音机
2021/03/02 无线电
PHP多文件上传类实例
2015/03/07 PHP
老生常谈PHP面向对象之命令模式(必看篇)
2017/05/24 PHP
php用户名的密码加密更安全的方法
2019/06/21 PHP
浅谈javascript 面向对象编程
2009/10/28 Javascript
用jquery设置按钮的disabled属性的实现代码
2010/11/28 Javascript
js中parseFloat(参数1,参数2)定义和用法及注意事项
2013/01/27 Javascript
javascript字符串替换函数如何一次性全部替换掉
2015/10/30 Javascript
CascadeView级联组件实现思路详解(分离思想和单链表)
2016/04/12 Javascript
url中的特殊符号有什么含义(推荐)
2016/06/17 Javascript
JavaScript与ActionScript3两者的同性与差异性
2016/09/22 Javascript
JavaScript reduce和reduceRight详解
2016/10/24 Javascript
vue实现可增删查改的成绩单
2016/10/27 Javascript
详谈Ajax请求中的async:false/true的作用(ajax 在外部调用问题)
2017/02/10 Javascript
Webpack打包慢问题的完美解决方法
2017/03/16 Javascript
浅谈Redux中间件的实践
2018/07/27 Javascript
微信小程序获取复选框全选反选选中的值(实例代码)
2019/12/17 Javascript
Vue 修改网站图标的方法
2020/12/31 Vue.js
[01:05:59]Mineski vs Secret 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.22
2019/09/05 DOTA
Python中集合的内建函数和内建方法学习教程
2015/08/19 Python
python实现list元素按关键字相加减的方法示例
2017/06/09 Python
Python推导式简单示例【列表推导式、字典推导式与集合推导式】
2018/12/04 Python
python3 批量获取对应端口服务的实例
2019/07/25 Python
解决python replace函数替换无效问题
2020/01/18 Python
基于tensorflow指定GPU运行及GPU资源分配的几种方式小结
2020/02/03 Python
python argparse模块通过后台传递参数实例
2020/04/20 Python
python能做哪些生活有趣的事情
2020/09/09 Python
python+openCV对视频进行截取的实现
2020/11/27 Python
Python plt 利用subplot 实现在一张画布同时画多张图
2021/02/26 Python
英国在线定制百叶窗网站:Swift Direct Blinds
2020/02/25 全球购物
商务英语专业毕业生自荐信
2013/11/05 职场文书
我的理想演讲稿
2014/04/30 职场文书
关于青春的演讲稿500字
2014/08/22 职场文书
学党史心得体会
2014/09/05 职场文书
护士实习自荐信
2015/03/06 职场文书
OpenCV实现反阈值二值化
2021/11/17 Java/Android