• Go sync.once


    作用

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

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

    main

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

    package main
    
    import "fmt"
    
    type container struct {
    	Num int
    }
    
    func main() {
    	c := container{Num: 666}
    	fmt.Println(c.Num)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    package 级别变量

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

    package container
    
    type container struct {
    	Num int
    }
    
    var C = container{Num: 1}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    init

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

    package container
    
    type container struct {
    	Num int
    }
    
    var C container
    
    func init()  {
    	C = container{Num: 666}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    实现原理

    • 原理还是比较简单的
      • 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()
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    缺点

    • 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))
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    增加接受初始化错误

    • 在 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)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    允许判断是否初始化

    使用 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())
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    如果不需要初始化异常的判断,可以在 **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())
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    JNI开发必学C/C++使用实践
    MySQL 表的增删查改
    关于标准帧和扩展帧的区别
    idea设置项目启动的JVM运行内存大小
    mysql-高级命令(1)和一些函数(悟已往之不谏,知来者之可追)
    Java知识梳理 第五章 程序控制结构
    K8s: 部署 kubernetes dashboard
    如何快速挣到一百万
    LibreOffice怎么打开导航页面
    在线虚拟机安装-云原生边缘计算KubeEdge安装配置(使用负载均衡器LoadBalancer)
  • 原文地址:https://blog.csdn.net/qq_29744347/article/details/133466086