go中sync.Once实现懒加载

 更新时间:2026年04月07日 10:23:58   作者:诺青235  
本文主要介绍了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懒加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang变量uint、int大小溢出后的结果方式

    golang变量uint、int大小溢出后的结果方式

    在Go语言中,变量的大小溢出后,`uint`类型会回绕到最小值,而`int`类型会回绕到最大值的相反数,例如,`uint8`溢出后会变成0,`int64`溢出后会变成最小的负数
    2024-12-12
  • 关于go语言编码需要放到src 文件夹下的问题

    关于go语言编码需要放到src 文件夹下的问题

    这篇文章主要介绍了go语言编码需要放到src 文件夹下的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Go中RPC远程过程调用的实现

    Go中RPC远程过程调用的实现

    本文主要介绍了Go中RPC远程过程调用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Golang设计模式之责任链模式讲解和代码示例

    Golang设计模式之责任链模式讲解和代码示例

    责任链是一种行为设计模式, 允许你将请求沿着处理者链进行发送, 直至其中一个处理者对其进行处理,本文就详细给大家介绍一下Golang 责任链模式,文中有详细的代码示例,需要的朋友可以参考下
    2023-06-06
  • Go语言如何利用Mutex保障数据读写正确

    Go语言如何利用Mutex保障数据读写正确

    这篇文章主要介绍了互斥锁的实现机制,以及 Go 标准库的互斥锁 Mutex 的基本使用方法,文中的示例代码讲解详细,需要的小伙伴可以参考一下
    2023-05-05
  • Go语言开发区块链只需180行代码(推荐)

    Go语言开发区块链只需180行代码(推荐)

    这篇文章主要介绍了Go语言开发区块链只需180行代码,文章中将不会涉及工作量证明算法(PoW)以及权益证明算法(PoS)这类的共识算法。需要的朋友可以参考下
    2018-05-05
  • 使用Go语言解决Scan空格结束输入问题

    使用Go语言解决Scan空格结束输入问题

    这篇文章主要为大家介绍了使用Go语言来解决Scan空格结束输入问题,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • omitempty在go中的使用方式

    omitempty在go中的使用方式

    在Go语言编程中,`omitempty`标记用于JSON编解码过程中控制字段是否被包含,当结构体字段标记为`omitempty`且字段值为空时,该字段不会出现在生成的JSON中,有助于优化JSON结构和减小数据体积,通过具体示例解释了`omitempty`的工作机制和实际效果
    2024-09-09
  • golang实现无锁队列的三种方式

    golang实现无锁队列的三种方式

    本文主要介绍了golang实现无锁队列的三种方式,包括基于CAS操作的简单有界队列、Michael-Scott算法的无锁链表队列及Go简化指针操作方法,感兴趣的可以了解一下
    2026-04-04
  • Golang HTTP 服务平滑重启及升级的思路

    Golang HTTP 服务平滑重启及升级的思路

    Golang HTTP服务在上线时,需要重新编译可执行文件,关闭正在运行的进程,然后再启动新的运行进程。这篇文章主要介绍了Golang HTTP 服务平滑重启及升级,需要的朋友可以参考下
    2020-04-04

最新评论