sync.once详解

超超为了能让婷婷过上幸福美满的生活,决定去大厂历练一番,下面是他去大厂面试时遇到的几个关于单例的问题。

一、认识单例

超超:您好,面试官~

面试官:你好,你平时开发是用 windows 还是 linux 居多?

超超:(哎呀,这是要问我linux问题吗,算了,如实回答吧) 我平时都是用windows开发的。

面试官:那你知道 windows 的任务管理器只能单开,但是cmd命令行可以开很多个,有想过这是为什么吗?

考点:单例的使用场景优缺点

超超:资源管理器在整个系统运行过程中不会因为不同的任务管理器内容改变而改变,因此为了节省资源全局只需要有一份,这是单例。

二、单例怎么用

面试官:你刚才说到了单例,你知道go里面怎么使用单例吗?

考点:go如何使用单例

超超:这个简单,举个例子,婷婷在购物时,android端和web端只有一个指向用户婷婷的对象

package main

import (
	"fmt"
	"sync"
)

//婷婷的淘宝客户端和web端都会指向婷婷这一个人
type Woman struct {
	name string
}

var (
	once sync.Once
	ting *Woman
)

func getTing() *Woman {
	once.Do(func() {
		ting = new(Woman)
		ting.name = "tingting"
		fmt.Println("newtingting")
	})
	fmt.Println("gettingting")
	return ting
}

func main() {
	for i := 0; i < 3; i++ {
		_ = getTing()
	}
}

结果

newtingting
gettingting
gettingting
gettingting
三、源码实现

面试官:那你能说说sync.Once是怎么实现的吗(:举个例子还秀起来了

考点:深入源码,进一步了解Once

超超:sync.Once是由Once结构体和其DodoSlow俩个方法实现的

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/x86),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

done是标识位,用来判断方法f是否被执行完,其初始值为0,当f执行结束时,done被设为1。

m做竞态控制,当f第一次执行还未结束时,通过m加锁的方式阻塞其他once.Do执行f

这里有个地方需要特别注意下,once.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)
	}
}

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

作用:通过原子操作判断o.done,如果o.done==0f未被执行完,进入doSlow(f func()),如果f执行完则退出Do()

入参:无

出参:无

  • doSlow(f func())方法

作用:通过加锁的方式,执行f,并在f执行结束时,将o.done置为1

入参:执行体f,通常为对象的创建或者模块数据加载

出参:无

面试官:你知道atomic.CompareAndSwapUint32(&o.done, 0, 1)的作用是什么吗?

考点:对sync包了解的广度

超超:CompareAndSwapUint32简称CAS,通过原子操作判断当o.done值等于0时,使o.done等于1并返回true,当o.done值不等于0,直接返回false

面试官:很好,那你能说说Do()方法中可以把atomic.LoadUint32直接替换为atomic.CompareAndSwapUint32吗?

考点:多线程思维

超超:这个是不可以的,因为f的执行是需要时间的,如果用CAS可能会导致f创建的对象尚未完成,其他地方就开始调用了。如图所示,A,B俩个协程都调用Once.Do方法,A协程先完成CAS将done值置为了1,导致B协程误以为对象创建完成,继而调用对象方法而出错。

在这里插入图片描述

这里doSlow中的o.done == 0判断,也需要注意一下,因为可能会出现A,B俩个协程都进行了LoadUint32判断,并且都是true,如果不进行第二次校验的话,对象会被new俩次

在这里插入图片描述

四、拓展

面试官:看来你对源码sync.Once的实现还比较熟悉,那你知道懒汉模式和饿汉模式吗?

考点:单例创建的延伸

超超:

饿汉模式:是指在程序启动时就进行数据加载,这样避免了数据冲突,也是线程安全的,但是这可能会造成内存浪费。比如在程序启动时就new一个 woman对象加载婷婷相关数据,当需要调用婷婷相关方法时,不用再创建对象。

懒汉模式:是指需要执行对象相关方法时,才主动去加载数据,这样做可以避免内存的浪费,比如当需要调用婷婷相关的方法时,才去new一个 woman对象加载婷婷相关数据,然后调用方法。

面试官:不错,倒是会一点花拳绣腿,那我们开始真正的面试吧。

超超:啊!?

未完待续 ~

欢迎关注我的公众号,查看超超面试最新进展
在这里插入图片描述

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
引用\[1\]和\[2\]提供了关于sync.Once的使用示例。sync.Once是Go语言中的一个同步原语,用于确保某个操作只执行一次。在示例中,sync.Once被用来保证onceBody函数只被执行一次。通过调用once.Do(onceBody),可以确保onceBody函数只会在第一次调用时执行,后续的调用都会被忽略。这在并发编程中非常有用,可以避免重复执行某个操作。 引用\[3\]提供了一个更具体的示例,展示了如何使用sync.Once来获取一个客户端对象。在这个示例中,getMyClient函数使用sync.Once来确保只有在第一次调用时才会创建一个新的MyClient对象,后续的调用都会返回同一个对象。 根据提供的代码片段,var UserSrvOnce sync.Once是一个sync.Once类型的变量声明。根据sync.Once的用途,可以推测UserSrvOnce可能是用来确保某个操作只执行一次的。具体的操作需要根据代码的上下文来确定。 #### 引用[.reference_title] - *1* [Golang sync.Once详解](https://blog.csdn.net/neweastsun/article/details/127562284)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Golang sync.Once 简介与用法](https://blog.csdn.net/K346K346/article/details/87622326)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值