Go 语言初始化最佳实践
在 Go 语言中, 有一个 init()
函数可以对程序进行包级别的初始化, 但 init()
函数有诸多不便, 例如: 无法返回错误, 进行耗时初始化时, 会增加程序启动时间。因此 init()
函数并不适用于所有初始化。
1.初始化方式
在程序进行初始化时,我们应该分析初始化的对象是为: 急切初始化
(如: 日志组件, 配置文件读取) 还是 延迟初始化
(如: 数据库连接, 消息队列连接) 。急切初始化大多情况不需要对外部进行连接,且被其他组件所依赖,这时使用 init()
函数进行初始化。延迟初始化对象为需要程序对外部进行连接,且耗时较长, 这时调用 sync.once
进行初始化保证并发安全。
特性 | 延迟初始化 | 使用 init 函数 进行 急切初始化 |
---|---|---|
执行时机 | 首次调用时执行 (懒加载) | 包被导入时自动执行,在 main 函数之前 (急加载) |
并发安全 | 调用 ,专为并发环境设计 | 包初始化由运行时控制,但后续对全局变量的并发访问需额外同步 |
错误处理 | 可通过封装返回 error (需自行实现) | 困难,通常只能 log.Fatal 或 panic |
资源消耗 | 按需分配,避免不必要的启动开销 | 程序启动即分配,可能增加启动时间和内存占用 |
典型应用场景 | 数据库连接池 、外部服务客户端 、重型单例对象 | 配置预加载 (轻量)、驱动注册 (如数据库驱动)、日志系统初始化 |
可控性 | 高,可灵活控制初始化时机和条件 | 低,由 Go 运行时控制执行顺序和时机 |
2.单例模式
单例模式是一种创建型设计模式,它的核心目标是确保一个结构体只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问或确保全局唯一性的场景中非常有用。
单例模式的关键在于三点:
- 唯一实例:单例结构体必须保证只创建一个对象实例。
- 自我创建:单例结构体需要自行创建这个实例。
- 全局访问:单例结构体必须提供一个允许全局访问该实例的方法。
实现上,通常不允许外部修改结构体,然后提供一个公共方法 (如 getInstance()
)来返回该类的唯一实例。
实现方式 | 描述 | 优点 | 缺点 | 并发安全 |
---|---|---|---|---|
饿汉式 | 类加载时就初始化实例。 | 实现简单,线程安全 | 未使用实例时也会创建,可能浪费内存 | 是 |
懒汉式 | 第一次调用时才创建实例。 | 延迟加载,节省资源 | 需加锁保证保证安全,性能有开销 | 是 |
init()
就是饿汉式 , 懒汉式 使用 sync.once
, 且 sync.once
内部的优化保证了性能和并发安全。
3.代码示例
饿汉式示例:
go
package conf
import (
"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 读到的数据序列化写入 config
if 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
}
懒汉式 示例:
go
package database
import (
"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.DB
err error
)
once.Do(func() {
c := conf.GetConfig()
user := c.Database.MySQL.User
passwd := c.Database.MySQL.Password
Host := c.Database.MySQL.MasterHost
Port := c.Database.MySQL.MasterPort
timeout := c.Database.MySQL.Timeout
DSN := 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 = db
return _mysql, err
}