單例模式恐怕是最為人熟知的一種設計模式了。它同樣也是建立型模式的一種。當某個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!