sync.once 是什么? | 青训营笔记

94 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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的地址,发现两次调用的地址是相同的。

调用DAO.png

接下来看看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的并发还有很多内容,还得持续摸索和学习。。