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!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM