首页 > 其他分享 >go语言sync.Once

go语言sync.Once

时间:2023-04-19 23:11:41浏览次数:25  
标签:Do sync done go path 执行 Once

go语言sync.Once

sync.Once 是 Go 标准库提供的使函数只执行一次的实现,常应用于单例模式,例如初始化配置、保持数据库连接等。作用与 init 函数类似,但有区别。

  • init 函数是当所在的 package 首次被加载时执行,若迟迟未被使用,则既浪费了内存,又延长了程序加载时间。

  • sync.Once 可以在代码的任意位置初始化和调用,因此可以延迟到使用时再执行,并发场景下是线程安全的。
    在多数情况下,sync.Once 被用于控制变量的初始化,这个变量的读写满足如下三个条件:

    • 当且仅当第一次访问某个变量时,进行初始化(写);
    • 变量初始化过程中,所有读都被阻塞,直到初始化完成;
    • 变量仅初始化一次,初始化完成后驻留在内存里。

数据结构

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

Once的数据结构很简单,done用来记录是否执行过,0为未执行,1为一致性,m互斥锁来保证原子操作

为什么done放在第一个字段

hot path:程序频繁执行的一些指令。
在源码中 done 字段频繁被访问(后面源码分析会讲到),所以它处在 hot path 上。
那为什么作为第一个字段就能减少 CPU 指令、提高性能呢?
因为结构体第一个字段的地址和结构体的地址是一样的,要访问第一个字段直接对结构体指针进行解引用即可,而访问后面的字段就要计算偏移量(前面字段所占字节空间 + 是否进行了内存对齐),就会增加 CPU 指令。

Do

func (o *Once) Do(f func()) {
	// Note: Here is an incorrect implementation of Do:
	//
	//	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
	//		f()
	//	}
	//
	// Do guarantees that when it returns, f has finished.
	// This implementation would not implement that guarantee:
	// given two simultaneous calls, the winner of the cas would
	// call f, and the second would return immediately, without
	// waiting for the first's call to f to complete.
	// This is why the slow path falls back to a mutex, and why
	// the atomic.StoreUint32 must be delayed until after f returns.

	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

源码注释中给出了一种 Do 的错误实现方式:使用 CAS 操作判断 f 是否已经执行,如果没有则执行,否则不执行。
看起来没什么问题,源码给出解释:Do 应该保证当自己返回时,f 已经执行完毕。当同时调用两次 Do 时,竞争成功者将原子地把 done 从 0 改为 1,失败者再进行 CAS 操作时发现不满足条件将直接返回,没有等成功者将 f 执行完。
这也就是为什么源码实现要用到互斥锁 mutex 以及为什么 atomic.StoreUint32 操作要等 f 返回后再执行(见下文 doSlow 分析)。

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

使用 defer 可以保证 f 先执行完,在 doSlow 返回时才执行 atomic.StoreUint32(&o.done, 1),当然 o.m.Unlock() 也是在 doSlow 返回时执行。
注:defer 的执行顺序是后进先出,也就是最后 defer 的函数,在返回时最先被执行。

看完源码,上来就先原子加载 done,上锁后还访问一次 done,因此说 done 在 hot path 上(填坑)。

总结

  1. Once可以确保方法执行一次,并且只执行一次
  2. 经常访问的字段可以放在结构体的第一位,可以提高访问效率

标签:Do,sync,done,go,path,执行,Once
From: https://www.cnblogs.com/zpf253/p/17335014.html

相关文章

  • 每周一坑-mongo每次启动后莫名关闭
    每周一坑-mongo每次启动后莫名关闭今天这个问题搞了大半天。。。明天找开发确认下功能是否已恢复正常。无意中发现某项目用完阿里云整个2T的oss对象存储,大家都知道,用完额度,超过的部分就会从用户余额去扣费。按道理来说,买的2T针对项目程序上传来说是够的,也不太可能从这里......
  • gltf version must be 2.0, got 1.0.1
    2015年10月19日,发布了glTF1.0规范glTF2.0于2017年6月发布,是对1.0版文件格式的完整检查,大多数工具都采用2.0版。参考1:https://blog.csdn.net/weixin_40676050/article/details/115033305参考2:https://zhuanlan.zhihu.com/p/166984169......
  • #yyds干货盘点#详解Django基础
    一:创建项目使用django-admin命令构建项目django-adminstartprojectHelloWorld如上使用django构建了一个HelloWorld项目二:项目结构我们可以看到HelloWorld项目结构如下|--HelloWorld||--__init__.py||--settings.py||--urls.py|`--wsgi.py`--manage.p......
  • gotenberg+ chromiumly + pdf.js 进行office 文档转换以及预览处理
    日常中office预览是一个比较常见的问题,基于微软的officeonline是一个选择,但是移动端效果不是很好就有pdf以及一些基于生成图片的方案也是不错的,以下是基于gotenberg+chromiumly的一个尝试简单说明gotenberg是基于golang开发的包装了Chromium以及LibreOffice的基于api......
  • node可以用nvm快速切换版本,golang如何快速切换版本?用gvm就行。
    使用gvm可以带来以下好处:快速切换Golang版本,方便进行版本测试和开发;可以在多个项目中同时使用不同版本的Golang包和工具,避免冲突;可以通过gvm管理不同版本的Golang,方便安装、卸载和更新;可以自由选择Golang的镜像源,下载更快。我使用的是win1064位系统,所以只考虑w......
  • go项目中数据库连接,以及redis连接
     文件:common/mysql.go数据库连接文件packagecommonimport("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/schema""log")varDB*gorm.DB//全局定义DBvardbErrerror//定义数据库错误funcinit(){dsn:=......
  • ARC159F Good Division【性质,DP,线段树】
    定义一个序列是好的当且仅当其可以通过每次删去一对相邻的不同的数把序列删空。给定一个长度为\(2n\)的序列\(a\),求有多少种划分方式使得每一段都是好的。答案对\(998244353\)取模。\(n\leq5\times10^5\),时限\(\text{5.0s}\)。先考虑什么样的数列是合法的,显然必要条......
  • 【中介者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
    简介中介者模式(MediatorPattern)是一种行为型模式。它限制对象之间的直接交互,它用一个中介对象来封装一系列的动作,以让对象之间进行交流。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。当一些对象和其他对象紧密耦合以致难以对其进......
  • Docker 运行 mongodb 无法连接 mongosh 问题
    场景在宿主机上依次执行以下命令,拉取mongo镜像,创建容器并运行dockerpullmongodockerrun--rm-d\--namemongo-test\mongo\bash进入容器中:dockerexec-itmongo-testbash执行使用mongosh连接mongo数据库的命令:mongosh报错:MongoNetworkError:......
  • google pay 配置sub/pub回调
        来源:https://blog.csdn.net/wujiesunlirong/article/details/122173321......