Go单例模式与Once源码实现

2022-12-08 139阅读 0评论

单例?=实现

type singleton Struct{}  var ( 	instance*singleton 	initialized uint32 	mu  sync.Mutex )  func Instance() *singleton { 	if atomic.LoadUInt32(&initialized) == 1 { 		return instance 	}  	mu.Lock() 	Defer mu.Unlock()  	if instance == nil { 		defer atomic.StoreUint32(&initialized, 1) 		instance = &singleton{} 	} 	return instance }

其中通用的代码提取出来,就成了标准库sync.Once的实现:

Type Once struct { 	done uint32 	msync.Mutex }  func (o *Once) Do(f func()) { 	if atomic.LoadUint32(&o.done) == 0 {  		o.m.Lock() 		defer o.m.Unlock()  		if o.done == 0 { 			defer atomic.StoreUint32(&o.done, 1) 			f() 		} 	} }

于是,使用sync.Once重新实现单例模式

var ( 	instance2 *singleton 	once sync.Once )  func Instance2() *singleton { 	once.Do(func() { 		instance2 = &singleton{} 	}) 	return instance2 }

sync.Once源码分析

1. lock并不会同步值

在lock和unlock之间修改值,并不会保证对其他协程是可见的,除非使用相同的Mutex加锁,想要同步值必须使用atomic;

lock可以通过串行化,使得两个协程的操作存在happen-before关系,从而是的操作可见

happen-before原则定义如下:

如果一个操作happens-before(之前发生)另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

2. Do执行一次

当第一次执行完Do之后,done设置成1,后面执行Do会直接跳过

3. Once执行Do后不准Copy

A Once must not be copied after first use.

sync.Once执行完Dodone已经设置成1了,copy出来的once执行Do会直接跳过

4. Do并发时阻塞

当两个或者多个协程同时调用Do时,先调用的协程执行,后面的协程会阻塞;

解释:以单例使用once的实现说明,两个协程同时调用Instance2(),先调用的协程执行创建并拿到返回值,后调用的阻塞,

​ 等到先调用的完成后再拿到返回值;

意义:这样的好处是防止后调用的协程拿到的是nil

源码说明:上面第二段代码13行使用defer,要等f()结束才会把done设置成1;其他协程并发调用Do时,done==0

​ 然后请求m.Lock()形成阻塞

5. Do递归死锁

如果Do中的方法调用当前once的Do会造成死锁,原因参考上面一点(sync.Mutex.Lock()时不可重入锁)

参考

Go语言高级编程Go1.16源码
免责声明
本站提供的资源,都来自网络,版权争议与本站无关,所有内容及软件的文章仅限用于学习和研究目的。不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负,我们不保证内容的长久可用性,通过使用本站内容随之而来的风险与本站无关,您必须在下载后的24个小时之内,从您的电脑/手机中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。侵删请致信E-mail:GOliszhou@gmail.com
$

发表评论

表情:
评论列表 (暂无评论,139人围观)

还没有评论,来说两句吧...