• GO 单例模式


     

    wiki百科: 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

    单例模式要实现的效果就是,对于应用单例模式的类,整个程序中只存在一个实例化对象

    go并不是一种面向对象的语言,所以我们使用结构体来替代

    有几种方式:

    • 懒汉模式

    • 饿汉模式

    • 双重检查锁机制

    下面拆分讲解:

    懒汉模式

    1. 构建一个示例结构体
       type example struct {
       	name string
       }
    1. 设置一个私有变量作为每次要返回的单例
      var instance *example
    1. 写一个可以获取单例的方法
        func GetExample() *example {
        
        	// 存在线程安全问题,高并发时有可能创建多个对象
        	if instance == nil {
        		instance = new(example)
        	}
        	return instance
        }
    1. 测试一下

        func main() {
        	s := GetExample()
        	s.name = "第一次赋值单例模式"
        	fmt.Println(s.name)
        
        	s2 := GetExample()
        	fmt.Println(s2.name)
        }

    懒汉模式存在线程安全问题,在第3步的时候,如果有多个线程同时调用了这个方法, 那么都会检测到instancenil,就会创建多个对象,所以出现了饿汉模式...

    饿汉模式

    与懒汉模式类似,不再多说,直接上代码

      // 构建一个结构体,用来实例化单例
      type example2 struct {
      	name string
      }
      
      // 声明一个私有变量,作为单例
      var instance2 *example2
      
      // init函数将在包初始化时执行,实例化单例
      func init() {
      	instance2 = new(example2)
      	instance2.name = "初始化单例模式"
      }
      
      func GetInstance2() *example2 {
      	return instance2
      }
      
      func main() {
      	s := GetInstance2()
      	fmt.Println(s.name)
      }
    

    饿汉模式将在包加载的时候就创建单例对象,当程序中用不到该对象时,浪费了一部分空间

    和懒汉模式相比,更安全,但是会减慢程序启动速度

    双重检查机制

    懒汉模式存在线程安全问题,一般我们使用互斥锁来解决有可能出现的数据不一致问题

    所以修改上面的GetInstance() 方法如下:

       var mux Sync.Mutex
       func GetInstance() *example {
           mux.Lock()                    
           defer mux.Unlock()
           if instance == nil {
               instance = &example{}
           }
          return instance
       }
    

    如果这样去做,每一次请求单例的时候,都会加锁和减锁,而锁的用处只在于解决对象初始化的时候可能出现的并发问题 当对象被创建之后,加锁就失去了意义,会拖慢速度,所以我们就引入了双重检查机制(Check-lock-Check), 也叫DCL(Double Check Lock), 代码如下:

      func GetInstance() *example {
          if instance == nil {  // 单例没被实例化,才会加锁 
              mux.Lock()
              defer mux.Unlock()
              if instance == nil {  // 单例没被实例化才会创建
      	            instance = &example{}
              }
          }
          return instance
      }
    

    这样只有当对象未初始化的时候,才会又加锁和减锁的操作

    但是又出现了另一个问题:每一次访问都要检查两次,为了解决这个问题,我们可以使用golang标准包中的方法进行原子性操作:

       import "sync"  
       import "sync/atomic"
       
       var initialized uint32
       
       func GetInstance() *example {
       	
       	  // 一次判断即可返回
          if atomic.LoadUInt32(&initialized) == 1 {
       		return instance
       	   }
           mux.Lock()
           defer mux.Unlock()
           if initialized == 0 {
                instance = &example{}
                atomic.StoreUint32(&initialized, 1) // 原子装载
       	}
       	return instance
       }

    以上代码只需要经过一次判断即可返回单例,但是golang标准包中其实给我们提供了相关的方法:

    sync.OnceDo方法可以实现在程序运行过程中只运行一次其中的回调,所以最终简化的代码如下:

     type example3 struct {
     	name string
     }
     
     var instance3 *example3
     var once sync.Once
     
     func GetInstance3() *example3 {
     
     	once.Do(func() {
     		instance3 = new(example3)
     		instance3.name = "第一次赋值单例"
     	})
     	return instance3
     }
     
     func main() {
     	e1 := GetInstance3()
     	fmt.Println(e1.name)
     
     	e2 := GetInstance3()
     	fmt.Println(e2.name)
     }
  • 相关阅读:
    不相交集合ADT
    Python基于比较的排序
    优先队列(堆)
    Python数据结构——散列表
    设计包含min函数的栈
    What really happens when you navigate to a URL
    browser process request
    Business vs Technology
    理解端口与IP
    Tor的教程
  • 原文地址:https://www.cnblogs.com/lurenq/p/11988920.html
Copyright © 2020-2023  润新知