Go 語言初始化最佳實踐
在 Go 語言中, 有一個 init()
函數可以對程序進行包級別的初始化, 但 init()
函數有諸多不便, 例如: 無法返回錯誤, 進行耗時初始化時, 會增加程序啟動時間。因此 init()
函數并不適用于所有初始化。
1.初始化方式
在程序進行初始化時,我們應該分析初始化的對象是為: 急切初始化
(如: 日志組件, 配置文件讀取) 還是 延遲初始化
(如: 數據庫連接, 消息隊列連接) 。急切初始化大多情況不需要對外部進行連接,且被其他組件所依賴,這時使用 init()
函數進行初始化。延遲初始化對象為需要程序對外部進行連接,且耗時較長, 這時調用 sync.once
進行初始化保證并發安全。
特性 | 延遲初始化 | 使用 init 函數 進行 急切初始化 |
---|---|---|
執行時機 | 首次調用時執行 (懶加載) | 包被導入時自動執行,在 main 函數之前 (急加載) |
并發安全 | 調用 ,專為并發環境設計 | 包初始化由運行時控制,但后續對全局變量的并發訪問需額外同步 |
錯誤處理 | 可通過封裝返回 error (需自行實現) | 困難,通常只能 log.Fatal 或 panic |
資源消耗 | 按需分配,避免不必要的啟動開銷 | 程序啟動即分配,可能增加啟動時間和內存占用 |
典型應用場景 | 數據庫連接池 、外部服務客戶端、重型單例對象 | 配置預加載(輕量)、驅動注冊(如數據庫驅動)、日志系統初始化 |
可控性 | 高,可靈活控制初始化時機和條件 | 低,由 Go 運行時控制執行順序和時機 |
2.單例模式
單例模式是一種創建型設計模式,它的核心目標是確保一個結構體只有一個實例,并提供一個全局訪問點來獲取這個實例。這種模式在需要控制資源訪問或確保全局唯一性的場景中非常有用。
單例模式的關鍵在于三點:
- 唯一實例:單例結構體必須保證只創建一個對象實例。
- 自我創建:單例結構體需要自行創建這個實例。
- 全局訪問:單例結構體必須提供一個允許全局訪問該實例的方法。
實現上,通常不允許外部修改結構體,然后提供一個公共方法(如 getInstance()
)來返回該類的唯一實例。
實現方式 | 描述 | 優點 | 缺點 | 并發安全 |
---|---|---|---|---|
餓漢式 | 類加載時就初始化實例。 | 實現簡單,線程安全 | 未使用實例時也會創建,可能浪費內存 | 是 |
懶漢式 | 第一次調用時才創建實例。 | 延遲加載,節省資源 | 需加鎖保證保證安全,性能有開銷 | 是 |
init()
就是餓漢式, 懶漢式 使用 sync.once
, 且 sync.once
內部的優化保證了性能和并發安全。
3.代碼示例
餓漢式示例:
package confimport ("fmt""github.com/joho/godotenv""github.com/spf13/viper""log""time"
)type Config struct {App AppConfig
}var _conf = &Config{}func init() {var v = viper.New()v.SetConfigName("StarMall")v.SetConfigType("toml")v.AddConfigPath("E:/starmall/")v.AutomaticEnv()// 讀取配置文件err := v.ReadInConfig()if err != nil {fmt.Println(err)log.Printf("config load Error: %v \n", err)} else {log.Println("configuration file was read successfully")}// 將 viper 讀到的數據序列化寫入 configif err := v.Unmarshal(&_conf); err != nil {now := time.Now()log.Printf("%v: viper Unmarshal err:%s \n", now.Format("2006-01-02 15:04:05"), err)}
}func GetConfig() *Config {return _conf
}
懶漢式 示例:
package databaseimport ("fmt"_ "github.com/go-sql-driver/mysql""github.com/jmoiron/sqlx""github.com/star-find-cloud/star-mall/conf"log "github.com/star-find-cloud/star-mall/pkg/logger""sync"
)type MySQL struct {Conn *sqlx.DB
}var (_mysql = &MySQL{}once sync.Once
)func initMysql() (*sqlx.DB, error) {var (_db *sqlx.DBerr error)once.Do(func() {c := conf.GetConfig()user := c.Database.MySQL.Userpasswd := c.Database.MySQL.PasswordHost := c.Database.MySQL.MasterHostPort := c.Database.MySQL.MasterPorttimeout := c.Database.MySQL.TimeoutDSN := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&parseTime=True&timeout=%ss", user, passwd, Host, Port, timeout)_db, err = sqlx.Connect("mysql", DSN)if err != nil {fmt.Println("Database error, please check the logs.")//fmt.Println(c.Database.MySQL)log.MySQLLogger.Errorf("MySQL master connect faild: %s \n", err)} else {log.MySQLLogger.Infof("MySQL master connection successful: %s\n", Host)}_db.SetMaxOpenConns(c.Database.MySQL.MaxOpenConns)_db.SetMaxIdleConns(c.Database.MySQL.MaxIdleConns)})return _db, err
}func NewMySQL() (*MySQL, error) {db, err := initMysql()_mysql.Conn = dbreturn _mysql, err
}