golang并发安全-sync.Once

什么是sync.Once

sync.Once 是 Go 语言中的一种同步原语,用于确保某个操作或函数在并发环境下只被执行一次。它只有一个导出的方法,即 Do,该方法接收一个函数参数。在 Do 方法被调用后,该函数将被执行,而且只会执行一次,即使在多个协程同时调用的情况下也是如此。

例子

Go 复制代码
func main() {
	var once sync.Once
	for i := 0; i < 5; i++ {
		go func(i int) {
			fun1 := func() {
				fmt.Printf("i:=%d\n", i)
			}
			once.Do(fun1)
		}(i)
	}
	time.Sleep(1 * time.Second)
}

无论执行多少次,仅返回 i:=0

看看源码

Go 复制代码
//Once 是一个只执行一次的对象。
//Once 首次使用后不得复制一次。
type Once struct {
    //done表示动作是否已执行。
    // 它在结构中是第一个,因为它在hot路径中使用。
    // 热路径在每个调用站点都是内联的
    done uint32
    m    Mutex
}

唯一的对外开放函数(Do)

Go 复制代码
func (o *Once) Do(f func()) {
   //one为0则表示未执行过,调用doSlow()方法初始化
	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

Do(f) 被调用多次,即使f在每次调用中都有不同的值,只有第一次调用才会调用f。

Go 复制代码
func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 { // 双重检查,避免 f 已被执行过
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

只有done == 0 才会被释放,并且使用defer保证 f()执行完会讲done置1

我们说说Once一些其他的事吧

不要嵌套 同一个once

Go 复制代码
func main() {
	var once sync.Once
	for i := 0; i < 5; i++ {
		once.Do(func() {
			once.Do(func() {
				fmt.Println(1)
			})
		})
	}
	time.Sleep(1 * time.Second)
}

直接报错:

作者在代码中提示我们:因为在对f的一个调用返回之前,不会返回对Do的调用,所以如果f导致调用Do,它将死锁。

解释 atomic.CompareAndSwapUint32 为什么不行

作者提到了 atomic.CompareAndSwapUint32 是存在问题:给定两个同时调用,cas的获胜者将调用f,第二个将立即返回,而无需等待第一个对f的调用完成。

Go 复制代码
func (o *Once) Do(f func()) {
	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
		f()
	}
}

虽然 cas 保证了同一时刻只有一个请求进入 if 判断执行 f()。但是其它的请求却没有等待 f() 执行完成就立即返回了。那么用户端在执行 once.Do 返回之后其实就可能存在 f() 还未完成,就会出现意料之外的错误。如下面例子

Go 复制代码
type OnceSelf struct {
	done uint32
}

func (o *OnceSelf) Do(f func()) {

	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {f() }
}

func main() {
	var b map[string]int
	var once OnceSelf
	go func() {
		once.Do(func() {
			b = make(map[string]int, 10)
		})
	}()
	b["1"] = 1
	fmt.Println(b)

}

会存在问题

init 和 once区别

  • init 函数是在文件包首次被加载的时候执行,且只执行一次
  • sync.Onc 是在代码运行中需要的时候执行,且只执行一次

链接

what-is-fast-path-slow-path-hot-path

what-does-hot-path-mean-in-the-context-of-sync-once

相关推荐
码云骑士10 分钟前
09-Python模块导入机制-sys.path与循环导入的死锁式排查
开发语言·python
星恒随风10 分钟前
C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念
开发语言·c++·笔记·学习
郝学胜-神的一滴14 分钟前
Qt 高级开发 031:QListWidget图标布局实战
开发语言·c++·qt·程序人生·软件构建·用户界面
caimouse15 分钟前
Reactos 第 8 章 结构化异常处理 — 8.4 软异常
服务器·开发语言·windows
艾莉丝努力练剑16 分钟前
【Qt】界面优化:绘图API
linux·运维·开发语言·网络·qt·tcp/ip·udp
牛油果子哥q18 分钟前
队列(Queue)深度精讲,先进先出原理、顺序/链式/循环队列、STL queue底层、栈队列互模拟与面试考点全解
开发语言·c++·面试
聆风吟º27 分钟前
【Python编程日志】Python基础数据类型完整梳理
开发语言·python·数据类型
keykey6.39 分钟前
逻辑回归:从回归到分类
开发语言·人工智能·机器学习
喵星人工作室41 分钟前
C++火影忍者1.1.8
开发语言·c++·游戏
shchojj41 分钟前
ChatGPT Prompt Engineering for Developers - Expanding
开发语言·python·prompt