这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
在实现项目的过程中,对着之前上课时老师提供的例子比葫芦画瓢,其中有一段代码让我不太理解,这里为什么会使用到sync.Once。
package repository
type UserDao struct {
}
var userDao *UserDao
var userOnce sync.Once
func NewUserDaoInstance() *UserDao {
userOnce.Do(
func() {
userDao = &UserDao{}
})
return userDao
}
func (*UserDao) QueryUserById(id int64) (*User, error){
...
在百度之后了解到,sync.Once的作用是使得Once的Do方法只执行一次
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
// output:
Only once
我们看到尽管onceBody被调用了十次,但只打印了一次,这就使人联想到设计模式中的单例模式,再次百度后发现,sync.Once确实可以使我们在go中方便地使用单例模式。 当NewUserDaoInstance()函数调用后,就生成了一个userDao的单例,以后每次调用userDao时都是使用它。
我因此还验证了一下,我在项目代码中连续两次调用了userDao中的方法,并打印了userDao的地址,发现两次调用的地址是相同的。
接下来看看sync.Once是怎么实现的,点开源码会发现:
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
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()
}
}
Once结构体中定义了一个done,用来标记是否已经初始化过了。 Do()检查done是否被标记过,如果没有被标记,则调用doSlow()初始化它,在初始化过程中使用了一个互斥锁,Mutex锁保证只有一个go routine在初始化,在初始化结束后释放锁。并发情况下,其他的go routine就会被阻塞在o.m.Lock()。
go的并发还有很多内容,还得持续摸索和学习。。