單例模式恐怕是最為人熟知的一種設計模式了。它同樣也是創建型模式的一種。當某個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只會有一個實例。代碼中有幾處可以注意下:
- 在
getInstance()
方法的起始處首先檢查了下singleInstance
是否為nil。這樣每次調用getInstance()
方法的時候可以避免執行“鎖”操作。因為“鎖”相關的操作比較耗資源,會影響性能,因此越少調用越好。 singleInstance
對象在“鎖”作用區間內創建,可以避免goroutines的影響。- 在獲取到“鎖”資源后,程序中又一次校驗了
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能夠創建一個
single
struct的實例。 - 輸出內容中有多行“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!