📚 單例模式概述
什麼是單例模式?
單例模式是一種創建型設計模式,確保一個類只有一個實例,並提供一個全局訪問點。
適用場景
- 數據庫連接池
- 配置管理器
- 日誌記錄器
- 緩存系統
- 線程池
🏗️ 基礎單例模式實現
1. 非併發安全的基礎實現
package singleton
// 單例結構體
type Singleton struct {
value string
}
// 包級私有實例
var instance *Singleton
// 獲取單例實例(非線程安全)
func GetInstance() *Singleton {
if instance == nil {
instance = &Singleton{
value: "initial value",
}
}
return instance
}
// 業務方法示例
func (s *Singleton) GetValue() string {
return s.value
}
func (s *Singleton) SetValue(value string) {
s.value = value
}
問題分析:
- 在併發環境下,多個 goroutine 可能同時檢查到
instance == nil - 導致創建多個實例,違反單例原則
🔒 併發安全單例模式實現
2. 互斥鎖實現(懶漢式)
package singleton
import (
"fmt"
"sync"
)
var (
instance *Singleton
once sync.Once
mu sync.Mutex
)
// 方法1:使用 sync.Mutex(雙重檢查鎖定)
func GetInstanceMutex() *Singleton {
if instance == nil { // 第一次檢查,避免不必要的鎖競爭
mu.Lock()
defer mu.Unlock()
if instance == nil { // 第二次檢查,確保實例只創建一次
instance = &Singleton{
value: "mutex instance",
}
fmt.Println("Singleton instance created with mutex")
}
}
return instance
}
3. sync.Once 實現(推薦)
// 方法2:使用 sync.Once(Go語言推薦方式)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{
value: "sync.Once instance",
}
fmt.Println("Singleton instance created with sync.Once")
})
return instance
}
📊 不同實現方式對比
性能測試代碼
package singleton
import (
"sync"
"testing"
)
// 基準測試:互斥鎖實現
func BenchmarkMutexSingleton(b *testing.B) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = GetInstanceMutex()
}()
}
wg.Wait()
}
// 基準測試:sync.Once 實現
func BenchmarkOnceSingleton(b *testing.B) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = GetInstance()
}()
}
wg.Wait()
}
性能對比結果
|
實現方式
|
併發安全
|
性能
|
代碼簡潔性
|
推薦度
|
|
基礎實現
|
❌ 不安全
|
⭐⭐⭐⭐⭐
|
⭐⭐⭐⭐⭐
|
❌ 不推薦
|
|
互斥鎖
|
✅ 安全
|
⭐⭐⭐
|
⭐⭐⭐
|
⭐⭐⭐
|
|
sync.Once
|
✅ 安全
|
⭐⭐⭐⭐⭐
|
⭐⭐⭐⭐⭐
|
⭐⭐⭐⭐⭐
|
🎯 完整的實戰示例
配置管理器單例
package config
import (
"encoding/json"
"os"
"sync"
)
// Config 配置結構體
type Config struct {
Database DatabaseConfig `json:"database"`
Server ServerConfig `json:"server"`
Redis RedisConfig `json:"redis"`
}
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
type ServerConfig struct {
Port int `json:"port"`
ReadTimeout int `json:"read_timeout"`
WriteTimeout int `json:"write_timeout"`
}
type RedisConfig struct {
Addr string `json:"addr"`
Password string `json:"password"`
DB int `json:"db"`
}
var (
configInstance *Config
configOnce sync.Once
configMu sync.RWMutex
)
// GetConfig 獲取配置單例
func GetConfig() *Config {
configOnce.Do(func() {
// 從配置文件加載配置
configInstance = loadConfig()
})
return configInstance
}
// 線程安全的配置獲取方法
func (c *Config) GetDatabaseConfig() DatabaseConfig {
configMu.RLock()
defer configMu.RUnlock()
return c.Database
}
// 線程安全的配置更新方法
func (c *Config) UpdateDatabaseConfig(newConfig DatabaseConfig) {
configMu.Lock()
defer configMu.Unlock()
c.Database = newConfig
}
// 加載配置文件
func loadConfig() *Config {
file, err := os.Open("config.json")
if err != nil {
// 返回默認配置
return &Config{
Database: DatabaseConfig{
Host: "localhost",
Port: 5432,
Username: "admin",
Password: "password",
DBName: "myapp",
},
Server: ServerConfig{
Port: 8080,
ReadTimeout: 30,
WriteTimeout: 30,
},
Redis: RedisConfig{
Addr: "localhost:6379",
Password: "",
DB: 0,
},
}
}
defer file.Close()
var config Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
panic(err)
}
return &config
}
數據庫連接池單例
package database
import (
"database/sql"
"fmt"
"sync"
_ "github.com/lib/pq"
)
type DatabaseManager struct {
db *sql.DB
mu sync.RWMutex
}
var (
dbInstance *DatabaseManager
dbOnce sync.Once
)
func GetDBManager() *DatabaseManager {
dbOnce.Do(func() {
db, err := sql.Open("postgres",
"host=localhost port=5432 user=admin password=secret dbname=myapp sslmode=disable")
if err != nil {
panic(fmt.Sprintf("Failed to connect to database: %v", err))
}
// 測試連接
if err = db.Ping(); err != nil {
panic(fmt.Sprintf("Failed to ping database: %v", err))
}
dbInstance = &DatabaseManager{db: db}
fmt.Println("Database connection pool initialized")
})
return dbInstance
}
// 線程安全的查詢方法
func (dm *DatabaseManager) Query(query string, args ...interface{}) (*sql.Rows, error) {
dm.mu.RLock()
defer dm.mu.RUnlock()
return dm.db.Query(query, args...)
}
// 線程安全的執行方法
func (dm *DatabaseManager) Exec(query string, args ...interface{}) (sql.Result, error) {
dm.mu.Lock()
defer dm.mu.Unlock()
return dm.db.Exec(query, args...)
}
// 關閉數據庫連接
func (dm *DatabaseManager) Close() error {
dm.mu.Lock()
defer dm.mu.Unlock()
return dm.db.Close()
}
🧪 單元測試
package singleton_test
import (
"singleton"
"sync"
"testing"
)
// 測試單例實例的唯一性
func TestSingletonUniqueness(t *testing.T) {
var wg sync.WaitGroup
instances := make([]*singleton.Singleton, 100)
for i := 0; i < 100; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
instances[index] = singleton.GetInstance()
}(i)
}
wg.Wait()
// 驗證所有實例都是同一個
firstInstance := instances[0]
for i, instance := range instances {
if instance != firstInstance {
t.Errorf("Instance at index %d is not the same as first instance", i)
}
}
}
// 測試併發安全性
func TestSingletonConcurrent(t *testing.T) {
const goroutines = 1000
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
instance := singleton.GetInstance()
instance.SetValue("test")
_ = instance.GetValue()
}()
}
wg.Wait()
}
🚀 最佳實踐總結
1. 首選 sync.Once
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{}
})
return instance
}
2. 避免全局變量污染
// 不好的做法:導出實例變量
var Instance *Singleton
// 好的做法:通過函數訪問
func GetInstance() *Singleton {
// ...
}
3. 考慮延遲初始化
type LazySingleton struct {
data []string
}
func (s *LazySingleton) loadData() {
// 延遲加載昂貴資源
if s.data == nil {
s.data = loadFromDatabase()
}
}
4. 處理初始化錯誤
var (
instance *Singleton
once sync.Once
initErr error
)
func GetInstance() (*Singleton, error) {
once.Do(func() {
instance, initErr = initializeSingleton()
})
return instance, initErr
}
func initializeSingleton() (*Singleton, error) {
// 初始化邏輯,可能返回錯誤
if err := someInitialization(); err != nil {
return nil, err
}
return &Singleton{}, nil
}
⚠️ 注意事項
- 避免過度使用單例 - 單例模式可能引入全局狀態,增加代碼耦合度
- 考慮依賴注入 - 在大型項目中,考慮使用依賴注入替代單例
- 測試困難 - 單例可能使單元測試變得複雜
- 生命週期管理 - 注意單例的初始化和清理時機
📝 總結
Go 語言中實現單例模式的最佳實踐是使用 sync.Once,它提供了:
- ✅ 完美的併發安全性
- ✅ 優秀的性能表現
- ✅ 簡潔的代碼實現
- ✅ 內置的線程安全保證