單例模式恐怕是最為人熟知的一種設計模式了。它同樣也是創建型模式的一種。當某個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都有可能會創建一個singlestruct實例。
完整代碼在這里:
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!
