zoukankan      html  css  js  c++  java
  • GoLang设计模式04

    单例模式恐怕是最为人熟知的一种设计模式了。它同样也是创建型模式的一种。当某个struct只允许有一个实例的时候,我们会用到这种设计模式。这个struct的唯一的实例被称为单例对象。下面是需要创建单例对象的一些场景:

    • 数据库实例:一般在开发中,对于一个应用,我们通常只需要一个数据库对象实例
    • 日志实例:同样,对于一个应用来说,日志操作对象也只需要一个实例

    单例对象通常在struct初始化的时候创建。通常,如果某个struct只需要创建一个实例的时候,会为其定义一个getInstance()方法,创建的单例实例会通过这个方法返回给调用者。

    因为Go语言中有goroutines,它会给单例模式的应用带来一些麻烦。我们在构建单例模式的时候必须要考虑到在多个goroutines访问struct的getInstance()方法的时候应该返回相同的实例。下面的代码演示了如何正确的创建一个单例对象:

    var lock = &sync.Mutex{}
    
    type single struct {
    }
    
    var singleInstance *single
    
    func getInstance() *single {
        if singleInstance == nil {
            lock.Lock()
            defer lock.Unlock()
            if singleInstance == nil {
                fmt.Println("Creting Single Instance Now")
                singleInstance = &single{}
            } else {
                fmt.Println("Single Instance already created-1")
            }
        } else {
            fmt.Println("Single Instance already created-2")
        }
        return singleInstance
    }
    

    以上的代码保证了single struct只会有一个实例。代码中有几处可以注意下:

    1. getInstance()方法的起始处首先检查了下singleInstance是否为nil。这样每次调用getInstance()方法的时候可以避免执行“锁”操作。因为“锁”相关的操作比较耗资源,会影响性能,因此越少调用越好。
    2. singleInstance对象在“锁”作用区间内创建,可以避免goroutines的影响。
    3. 在获取到“锁”资源后,程序中又一次校验了singleInstance对象是否为空。这是因为可能会有多个goroutines通过第一次校验,二次校验可以保证只有一个goroutine创建单例,不然每个goroutine都有可能会创建一个single struct实例。

    完整代码在这里:

    single.go

    import (
    	"fmt"
    	"sync"
    )
    
    var lock = &sync.Mutex{}
    
    type single struct {
    }
    
    var singleInstance *single
    
    func GetInstance() *single {
    	if singleInstance == nil {
    		lock.Lock()
    		defer lock.Unlock()
    		if singleInstance == nil {
    			fmt.Println("Creating Single Instance Now")
    			singleInstance = &single{}
    		} else {
    			fmt.Println("Single Instance already created-1")
    		}
    	} else {
    		fmt.Println("Single Instance already created-2")
    	}
    	return singleInstance
    }
    

      main.go

    import (
    	"fmt"
    )
    
    func main() {
    	for i := 0; i < 100; i++ {
    		go GetInstance()
    	}
    	// Scanln is similar to Scan, but stops scanning at a newline and
    	// after the final item there must be a newline or EOF.
    	fmt.Scanln()
    }
    

      输出内容:

    Creating Single Instance Now
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-2
    ...
    

    简单说明下:

    • 输出内容中只有一行“Creating Single Instance Now”这样的输出,这说明只有一个goroutine能够创建一个singlestruct的实例。
    • 输出内容中有多行“Single Instance already created-1”,说明有多个goroutines通过第一次检查singleInstance对象是否为空的校验,它本来都有机会创建单例。
    • 最后输出的都是“Single Instance already created-2”,意味着单例已创建完成,之后的goroutines都无法再通过首次校验。

    除了锁+二次校验的方式,还有其它创建单例的方法,我们来看一下:

    基于init()函数

    init()函数中创建单例。因为一个包中每个文件的init()函数都只会调用一次,这样就可以保证只有一个实例会被创建。看下代码:

    import (
    	"fmt"
    	"log"
    )
    
    type single struct {
    }
    
    var singleInstance *single
    
    func init() {
    	fmt.Println("Creating Single Instance Now")
    	singleInstance = &single{}
    }
    
    func GetInstance() *single {
    	if singleInstance == nil {
    		log.Fatal("Single Instance is nil")
    	} else {
    		fmt.Println("Single Instance already created-2")
    	}
    	return singleInstance
    }
    

    这应该就是go语言中的懒汉式单例创建方法了。如果不介意过早创建实例造成的资源占用,推荐使用这种方法创建单例。

    通过sync.Once

    sync.Once中的代码会被保证只执行一次,这完全可以用来创建单例。代码如下:

    import (
    	"fmt"
    	"sync"
    )
    
    var once sync.Once
    
    type single struct {
    }
    
    var singleInstance *single
    
    func GetInstance() *single {
    	if singleInstance == nil {
    		once.Do(
    			func() {
    				fmt.Println("Creating Single Instance Now")
    				singleInstance = &single{}
    			})
    		fmt.Println("Single Instance already created-1")
    	} else {
    		fmt.Println("Single Instance already created-2")
    	}
    	return singleInstance
    }
    

    相比二次校验的方式,这里的代码可以说非常简洁了。这也是我非常建议使用的一种单例创建方式。

    输出内容为:

    Creating Single Instance Now
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-1
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-1
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    Single Instance already created-2
    ...
    

    简单说明下: 

    • 输出的内容和二次校验的方式差不多,仍然存在多行“Single Instance already created-1”这样的输出,说明有多个goroutine通过了if校验
    • 输出内容中只有一行“Creating Single Instance Now”,说明只有一个goroutine能够创建实例。

    代码已上传至GitHub:zhyea / go-patterns / singleton-pattern

    End!


    仅是学习笔记,难免出错,望不吝指点
  • 相关阅读:
    C# ConfigurationManager 类的使用
    git强制推送命令
    git强制推送命令
    《信息学奥赛一本通》 高精除以低精。输入两个正整数,求它们的商(做整除)。
    《信息学奥赛一本通》 高精除以低精。输入两个正整数,求它们的商(做整除)。
    《信息学奥赛一本通》 高精度乘法。输入两个正整数,求它们的积。
    《信息学奥赛一本通》 高精度乘法。输入两个正整数,求它们的积。
    《信息学奥赛一本通》 高精度乘法。输入两个正整数,求它们的积。
    《C champion》 为什么要学习C语言
    《C champion》 为什么要学习C语言
  • 原文地址:https://www.cnblogs.com/amunote/p/15253251.html
Copyright © 2011-2022 走看看