Go sync.once

作用

  • 保证只进行一次初始化(并发安全)
  • 常用于单例的实例化

常用的几种安全单例实例化方式

main

入口只会执行一次,所以是安全的

package main

import "fmt"

type container struct {
	Num int
}

func main() {
	c := container{Num: 666}
	fmt.Println(c.Num)
}

package 级别变量

包级别变量,也只会在包引入时执行一次

package container

type container struct {
	Num int
}

var C = container{Num: 1}

init

包初始化函数,也只会执行一次,所以也是安全的

package container

type container struct {
	Num int
}

var C container

func init()  {
	C = container{Num: 666}
}

sync.once

package main

import (
	"fmt"
	"sync"
)

type container struct {
	Num int
}

var c *container

var one sync.Once

func getContainer() *container {
	one.Do(func() {
		c = &container{Num: 666}
	})
	return c
}

func main() {
	c1 := getContainer()
	fmt.Println(c1.Num) //666
	c1.Num = c1.Num - 1
	fmt.Println(c1.Num) //665
	c2 := getContainer()
	fmt.Println(c2.Num) //665
}

实现原理

  • 原理还是比较简单的
    • 20行 done 标识来维护是否执行过
    • 57 行 atomic.LoadUint32(&o.done) == 0, 未执行过的才能执行 doSlow
    • doSlow 中使用了 sync.Mutex 防止并发执行
    • f() 执行逻辑,修改 done = 1, 释放锁
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sync

import (
	"sync/atomic"
)

// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
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/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// 	var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// 	config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
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(f func()),就算f出错,done 也会标记为执行过
    • f 也没有输出 error
    • 这种情况在单例延迟加载的情况下会导致单例不可用
  • 我们想要的单例可能是成功创建过一次的,而不是执行过一次的
  • 下面的例子是模拟运行时的(第一次)单例未能从配置中心拉到配置而导致的不能成功实例化,第二次能拉到配置,却不能实例化
package main

import (
	"fmt"
	"sync"
)

var once sync.Once

type Instance struct {
	Name string
}

func (receiver *Instance) Print() {
	fmt.Println("name:", receiver.Name)
}

var i *Instance

func getInstance(err bool) *Instance {
	//假设网络波动

	once.Do(func() {
		if err {
			//假设网络波动,未能从配置中心拉取到配置
			return
		}
		i = &Instance{Name: "假设网络波动"}
	})
	return i
}

func Logic(i *Instance) {
	if i != nil {
		i.Print()
	}
}

func main() {
	Logic(getInstance(true))
	Logic(getInstance(false))
}

增加接受初始化错误

  • 在 sync.Once 的源码基础上进行修改
    • Do 接收的函数 f() 变为可接受错误返回的 f() error
    • doSlowf() 执行成功后,才会进行 atomic.StoreUint32(&o.done, 1)
package main

import (
	"errors"
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

type fn func() error

type MyOnce struct {
	done uint32
	m    sync.Mutex
}

func (o *MyOnce) Do(f fn) error {
	if atomic.LoadUint32(&o.done) == 0 {
		return o.doSlow(f)
	}
	return nil
}

func (o *MyOnce) doSlow(f fn) error {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		if err := f(); err == nil {
			atomic.StoreUint32(&o.done, 1)
		} else {
			return err
		}
	}
	return nil
}

type Instance struct {
	Name string
}

func (receiver *Instance) Print() {
	fmt.Println("name:", receiver.Name)
}

var once MyOnce
var i *Instance

func getInstance(mock bool) (*Instance, error) {
	err := once.Do(func() error {
		if mock {
			return errors.New("假设网络波动,未能从配置中心拉取到配置")
		}
		i = &Instance{Name: fmt.Sprintf("假设网络波动,rand:%v", rand.Int())}
		return nil
	})
	return i, err
}

func Logic(i *Instance, err error) {
	if err == nil {
		i.Print()
	}
}

func main() {
	go func() {
		Logic(getInstance(true))
	}()
	go func() {
		Logic(getInstance(true))
	}()
	go func() {
		Logic(getInstance(false))
	}()
	go func() {
		Logic(getInstance(false))
	}()
	go func() {
		Logic(getInstance(true))
	}()
	go func() {
		Logic(getInstance(true))
	}()

	time.Sleep(2 * time.Second)
}

允许判断是否初始化

使用 atomic.LoadUint32 来判断初始化标识done是否完成

package main

import (
	"errors"
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
)

type fn func() error

type MyOnce struct {
	done uint32
	m    sync.Mutex
}

func (o *MyOnce) Do(f fn) error {
	if atomic.LoadUint32(&o.done) == 0 {
		return o.doSlow(f)
	}
	return nil
}

func (o *MyOnce) doSlow(f fn) error {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		if err := f(); err == nil {
			atomic.StoreUint32(&o.done, 1)
		} else {
			return err
		}
	}
	return nil
}

//初始化过
func (o *MyOnce) isInitiated() bool {
	return atomic.LoadUint32(&o.done) == 1
}

type Instance struct {
	Name string
}

func (receiver *Instance) Print() {
	fmt.Println("name:", receiver.Name)
}

var once MyOnce
var i *Instance

func getInstance(mock bool) (*Instance, error) {
	err := once.Do(func() error {
		if mock {
			return errors.New("假设网络波动,未能从配置中心拉取到配置")
		}
		i = &Instance{Name: fmt.Sprintf("假设网络波动,rand:%v", rand.Int())}
		return nil
	})
	return i, err
}

func Logic(i *Instance, err error) {
	if err == nil {
		i.Print()
	}
}

func main() {
	Logic(getInstance(true))
	fmt.Printf("是否已经初始化过%v \n", once.isInitiated())
	Logic(getInstance(false))
	fmt.Printf("是否已经初始化过%v \n", once.isInitiated())
}

如果不需要初始化异常的判断,可以在 **sync.Once** 上进行扩展
由于 **done****sync.Once** 中处于第一个,所以直接通过 **unsafe.Pointer** 获取

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"unsafe"
)

type MyOnce struct {
	sync.Once
}

func EmptyFunc() {

}

func (o *MyOnce) isInitiated() bool {
	return atomic.LoadUint32((*uint32)(unsafe.Pointer(&o.Once))) == 1
}

var once MyOnce

func main() {
	fmt.Printf("init:%v \n", once.isInitiated())
	once.Do(EmptyFunc)
	fmt.Printf("init:%v \n", once.isInitiated())
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值