ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

golang单例模式

2022-04-28 23:35:07  阅读:208  来源: 互联网

标签:instanse 单例 singler sync 模式 golang done func Once


定义

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式,可以看到定义简单,算是设计模式中最简单的一个模式了。

饿汉模式

即还未使用该对象时,对象已经创建完成。
方法是通过golang 的init函数,在导包时就自动执行。

package mian

import "fmt"

var instanse *singler

type singler struct {
    Name string
}

func NewSingler()*singler{
    return instanse
}

func init() {
    instanse = new(singler)
    instanse.Name = "test"
}


func main() {
    singler := NewSingler()
    fmt.Println(singler.Name)
}

懒汉模式

即用户需要的时候再创建,这里采用锁的模式去创建,为了减少锁的判断的次数,可以采用双重判断机制。在对象创建完成后,后续不需要加锁处理 。

package main

import (
    "fmt"
    "sync"
)

var instanse *singler
var mutex sync.Mutex

type singler struct {
    Name string
}

func NewSingler()*singler {
    if instanse == nil{
        mutex.Lock()
        defer mutex.Unlock()
        if instanse == nil{
            instanse = new(singler)
            instanse.Name = "test"
        }
    }
    return instanse
}

func main() {
    singler := NewSingler()
    fmt.Println(singler.Name)
}

必须要DCL 

双重检查加锁单例模式为什么两次if判断?

 

 

 

java 单例模式中双重检查锁定 volatile 的作用?
[推荐阅读]java 单例模式中双重检查锁定 volatile 的作用?
参考URL: https://www.zhihu.com/question/56606703?sort=created

volatile 是保证了可见性还是有序性?

主要是禁止重排序,初始化一个实例(SomeType st = new SomeType())在java字节码中会有4个步骤,

申请内存空间,
初始化默认值(区别于构造器方法的初始化),
执行构造器方法
连接引用和实例。
这4个步骤后两个有可能会重排序,1234 1243都有可能,造成未初始化完全的对象发布。

为什么要禁止重排序?
确保先执行构造器方法,再将引用和实例连接到一起。如果没有禁止重排序,会导致另一个线程可能获取到尚未构造完成的对象。

为什么没有起到可见性的作用?
JSR-133
An unlock on a monitor happens before every subsequent lock on that same monitor
第二次非null判断是在加锁以后,则根据这一条,另一个线程一定能看到这个引用被赋值。所以即使没有volatile,依旧能保证可见性。

总结: DCL单例中 1、synchronized已经保证了可见性,不需要volatile来保证。2、volatile的作用是禁止重排序。 因此,主要是为了保证原子操作。
————————————————
原文链接:https://blog.csdn.net/inthat/article/details/107807343

https://blog.csdn.net/m0_37681974/article/details/108139809

 

 

懒汉模式之sync.Once

在上面的代码中,是我们自己实现的防止创建多个的情况,在golang的包中有个很好用的工具sync.Once,它可以保证只执行一次。代码如下:

 

sync.Once.Do(f func())是一个挺有趣的东西,能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次。

 

package main

import (
    "fmt"
    "sync"
)

var once sync.Once = sync.Once{}
var instanse *singler

type singler struct {
    Name string
}

func NewSingler() *singler {
    once.Do(func() {
        instanse = new(singler)
        instanse.Name = "这是一个单例"
    })
    return instanse
}

func main() {
    singler := NewSingler()
    fmt.Println(singler.Name)
}
package main
import (
   "fmt"
   "sync"
)
var once sync.Once
var con string
func main() {
   once.Do(func() {
      con = "hello Test once.Do"
   })
   fmt.Println(con)
}

之前提到过 Go 的并发辅助对象:WaitGroup。同样的, sync.Once 也是 Go 官方的一并发辅助对象,它能够让函数方法只执行一次,达到类似 init 函数的效果。我们来看看它的简单用法:

func main() {
 var once sync.Once
 onceFunc := func() {
  fmt.Println("Only once")
 }

 for i := 0; i < 10; i++ {
  once.Do(onceFunc)
 }
}

这里执行后我们将只看到一次 Only once 的打印信息,这就是 sync.Once 的一次性效果

源码分析

接下来分析 sync.Do 究竟是如何实现的,它存储在包sync下 once.go 文件中,源代码如下:

// sync/once.go

type Once struct {
   done uint32 // 初始值为0表示还未执行过,1表示已经执行过
   m    Mutex 
}
func (o *Once) Do(f func()) {
   // 判断done是否为0,若为0,表示未执行过,调用doSlow()方法初始化
   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()
   // 采用双重检测机制 加锁判断done是否为零
   if o.done == 0 {
      // 执行完f()函数后,将done值设置为1
      defer atomic.StoreUint32(&o.done, 1)
      // 执行传入的f()函数
      f()
   }
}

,第一部分为 Once 的结构体组成结构,第二部分为 Do 函数的实现原理,我会在代码上加上注释,保证用心阅读完都有收获。

结构体

type Once struct {
   done uint32 // 初始值为0表示还未执行过,1表示已经执行过
   m    Mutex 
}

首先定义一个struct结构体 Once ,里面存储两个成员变量,分别为 donem

done成员变量

  • 1表示资源未初始化,需要进一步初始化
  • 0表示资源已初始化,无需初始化,直接返回即可

m成员变量

  • 为了防止多个goroutine调用 doSlow() 初始化资源时,造成资源多次初始化,因此采用 Mutex 锁机制来保证有且仅初始化一次

Do

func (o *Once) Do(f func()) {
   // 判断done是否为0,若为0,表示未执行过,调用doSlow()方法初始化
   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()
   // 采用双重检测机制 加锁判断done是否为零
   if o.done == 0 {
      // 执行完f()函数后,将done值设置为1
      defer atomic.StoreUint32(&o.done, 1)
      // 执行传入的f()函数
      f()
   }

调用 Do 函数时,首先判断done值是否为0,若为1,表示传入的匿名函数 f() 已执行过,无需再次执行;若为0,表示传入的匿名函数 f() 还未执行过,则调用 doSlow() 函数进行初始化。

在 doSlow() 函数中,若并发的goroutine进入该函数中,为了保证仅有一个goroutine执行 f() 匿名函数。为此,需要加互斥锁保证只有一个goroutine进行初始化,同时采用了双检查的机制(double-checking),再次判断 o.done 是否为 0,如果为 0,则是第一次执行,执行完毕后,就将 o.done 设置为 1,然后释放锁。

即使此时有多个 goroutine 同时进入了 doSlow 方法,因为双检查的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f。

这样既保证了并发的 goroutine 会等待 f 完成,而且还不会多次执行 f。




参考:https://www.jianshu.com/p/c8190ca4b3bd

 

标签:instanse,单例,singler,sync,模式,golang,done,func,Once
来源: https://www.cnblogs.com/youxin/p/16204993.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有