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 24 Golang
golang中的空slice案例
Apr 27 Golang
golang在GRPC中设置client的超时时间
Apr 27 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
golang 实现Location跳转方式
May 02 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
入门学习Go的基本语法
Jul 07 Golang
Golang 入门 之url 包
May 04 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
Go语言编译原理之变量捕获
Aug 05 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 5.0对象模型深度探索之类的静态成员
2008/03/27 PHP
php 学习资料零碎东西
2010/12/04 PHP
php中计算未知长度的字符串哪个字符出现的次数最多的代码
2012/08/14 PHP
thinkphp框架下404页面设置 仅三步
2016/05/14 PHP
PHP实现的最大正向匹配算法示例
2017/12/19 PHP
JQuery的自定义事件代码,触发,绑定简单实例
2013/08/01 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
2013/12/16 Javascript
JS中判断null、undefined与NaN的方法
2014/03/24 Javascript
jQuery的context属性用法实例
2014/12/27 Javascript
iframe中子父类窗口调用JS的方法及注意事项
2015/08/25 Javascript
Bootstrap每天必学之级联下拉菜单
2016/03/27 Javascript
Javascript操作表单实例讲解(下)
2016/06/20 Javascript
JS判断form内所有表单是否为空的简单实例
2016/09/09 Javascript
JS实现的DIV块来回滚动效果示例
2017/02/07 Javascript
以BootStrap Tab为例写一个前端组件
2017/07/25 Javascript
阿里大于短信验证码node koa2的实现代码(最新)
2017/09/07 Javascript
vue实现文件上传功能
2018/08/13 Javascript
深入浅析ng-bootstrap 组件集中 tabset 组件的实现分析
2019/07/19 Javascript
vue全局使用axios的操作
2020/09/08 Javascript
[55:25]VGJ.T vs Optic Supermajor小组赛D组 BO3 第三场 6.3
2018/06/04 DOTA
对于Python编程中一些重用与缩减的建议
2015/04/14 Python
Python实现自定义函数的5种常见形式分析
2018/06/16 Python
正则给header的冒号两边参数添加单引号(Python请求用)
2019/08/09 Python
python 爬取疫情数据的源码
2020/02/09 Python
python默认参数调用方法解析
2020/02/09 Python
django配置app中的静态文件步骤
2020/03/27 Python
如何将tensorflow训练好的模型移植到Android (MNIST手写数字识别)
2020/04/22 Python
生产现场工艺工程师岗位职责
2013/11/28 职场文书
环保专业大学生职业规划设计
2014/01/10 职场文书
优秀广告词大全
2014/03/19 职场文书
行政内勤岗位职责
2014/04/07 职场文书
表彰大会策划方案
2014/05/13 职场文书
运动会观后感
2015/06/09 职场文书
同意报考公务员证明
2015/06/17 职场文书
团队拓展训练心得体会
2016/01/12 职场文书
MySQL EXPLAIN输出列的详细解释
2021/05/12 MySQL