go中sync.Once实现懒加载
懒加载概念
懒加载(Lazy Loading)是一种延迟初始化技术,核心思想是在真正需要时才创建或初始化对象,而不是在程序启动时就完成所有初始化工作。
核心特征:
- 延迟初始化:对象在第一次使用时才被创建
- 资源优化:避免不必要的资源消耗
- 启动加速:减少应用程序启动时间
// 传统初始化 - 启动时立即执行
var db *Database = initDatabase()
// 懒加载 - 使用时才初始化
var db *Database
var once sync.Once
func GetDB() *Database {
once.Do(func() {
db = initDatabase()
})
return db
}适用场景
高成本资源初始化
- 数据库连接池建立
- 网络连接建立
- 大型配置文件解析
- 外部服务客户端初始化
sync.Once 原理
sync.Once 是 Go 语言标准库中提供的一个确保某个操作只执行一次的并发原语。其核心设计目标是在并发环境下保证初始化函数有且仅有一次成功执行,同时保持高性能。
数据结构
type Once struct {
done uint32 // 原子标志位
m Mutex // 互斥锁
}实现模式详解
带错误处理的懒加载
type Database struct {
conn *sql.DB
once sync.Once
err error
}
func (db *Database) init() {
db.conn, db.err = sql.Open("mysql", "user:pass@/dbname")
if db.err == nil {
db.err = db.conn.Ping()
}
}
func (db *Database) GetConnection() (*sql.DB, error) {
db.once.Do(db.init)
return db.conn, db.err
}配置驱动的懒加载
初始化参数(配置)在运行时动态传入,懒加载过程依赖外部传入的配置信息。
特点:
- 配置参数作为方法参数传递
- 同一实例可接受不同配置(但只生效第一次)
- 更灵活,支持运行时配置
type ConfigClient struct {
config *Config
client *Client
once sync.Once
err error
}
func (c *ConfigClient) lazyInit(config *Config) {
c.config = config
c.client, c.err = NewClient(config.Endpoint, config.Timeout)
}
func (c *ConfigClient) GetClient(config *Config) (*Client, error) {
c.once.Do(func() { c.lazyInit(config) })
return c.client, c.err
}适用场景:
- 依赖外部配置的初始化
- 动态参数传递
- 多环境配置支持
连接驱动的懒加载
配置在实例创建时固定,懒加载过程使用预定义的配置建立连接。
特点:
- 配置在构造时确定
- 初始化过程完全内部化
- 更稳定,配置不可变
问题
func newClientFromConfig() MongoRepository {
cfg, err := loadConfigFromEnv() // 在创建时加载配置
if err != nil {
return &mongoClient{err: err}
}
return &mongoClient{config: cfg} // 配置在构造时固定
}
func (c *mongoClient) lazyInit() error {
c.once.Do(func() {
// 使用预先设置的 c.config,不接收外部参数
clientOptions := options.Client().ApplyURI(c.config.URI)
// ... 使用固定配置建立连接
})
}全局单例懒加载
var (
globalClient *Client
globalOnce sync.Once
globalErr error
)
func GetGlobalClient() (*Client, error) {
globalOnce.Do(func() {
globalClient, globalErr = NewClient()
})
return globalClient, globalErr
}实际使用
MongoDB 客户端实现
package mongo
import (
"context"
"sync"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Client struct {
config *Config
client *mongo.Client
database *mongo.Database
once sync.Once
err error
}
func NewLazyClient(config *Config) *Client {
return &Client{config: config}
}
func (c *Client) lazyInit() {
ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(c.config.URI))
if err != nil {
c.err = err
return
}
// 验证连接
if err = client.Ping(ctx, nil); err != nil {
client.Disconnect(context.Background())
c.err = err
return
}
c.client = client
c.database = client.Database(c.config.Database)
}
func (c *Client) GetCollection(name string) (*mongo.Collection, error) {
c.once.Do(c.lazyInit)
if c.err != nil {
return nil, c.err
}
return c.database.Collection(name), nil
}
// 使用示例
func main() {
client := NewLazyClient(&Config{
URI: "mongodb://localhost:27017",
Database: "test",
Timeout: 10 * time.Second,
})
// 第一次调用时才会真正建立连接
collection, err := client.GetCollection("users")
if err != nil {
panic(err)
}
// 使用 collection...
}Redis 客户端实现
package redis
import (
"context"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
type LazyClient struct {
config *RedisConfig
client redis.UniversalClient
once sync.Once
err error
}
func (lc *LazyClient) initClient() {
if len(lc.config.Addrs) == 1 {
lc.client = redis.NewClient(&redis.Options{
Addr: lc.config.Addrs[0],
Password: lc.config.Password,
DB: lc.config.DB,
})
} else {
lc.client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: lc.config.Addrs,
Password: lc.config.Password,
})
}
// 测试连接
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := lc.client.Ping(ctx).Err(); err != nil {
lc.client.Close()
lc.err = err
}
}
func (lc *LazyClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
lc.once.Do(lc.initClient)
if lc.err != nil {
return lc.err
}
return lc.client.Set(ctx, key, value, expiration).Err()
}
func (lc *LazyClient) Get(ctx context.Context, key string) (string, error) {
lc.once.Do(lc.initClient)
if lc.err != nil {
return "", lc.err
}
return lc.client.Get(ctx, key).Result()
}sync.Once 在提供线程安全的同时,保持了优异的性能表现,是 Go 语言中实现懒加载的首选方案。
到此这篇关于go中sync.Once实现懒加载的文章就介绍到这了,更多相关go sync.Once懒加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论