单例模式(Singleton Pattern)工程规范与实现(Go / Rust 双语言)
单例模式(Singleton Pattern)工程规范与实现(Go / Rust 双语言)
1 概述
1.1 定义
单例模式属于创建型设计模式,保证一个类在整个进程中仅有唯一实例,对外提供全局统一访问入口。
核心约束:私有化构造能力,阻止外部随意新建实例,统一管控实例生命周期。
工程简化角色:
- Singleton(单例对象):目标业务结构体
- Instance Accessor(访问入口):提供全局获取唯一实例的方法,内置延迟初始化逻辑
1.2 适用场景
推荐使用
- 资源独占型组件:数据库连接池、HTTP 客户端、日志管理器、配置管理器;
- 需要全局统一状态、全局共享一份资源;
- 对象创建成本高(IO、网络、大量内存分配),希望只初始化一次。
避免滥用
- 无状态轻量对象,可随意新建;
- 需要多实例、多套独立配置;
- 单元测试难以 Mock(单例天然强耦合,优先依赖注入替代)。
1.3 解决核心痛点
- 避免重复创建高开销对象,节约资源;
- 全局统一访问点,统一初始化逻辑;
- 控制资源竞争,保证独占资源不会被多次抢占。
重要提醒:单例属于「全局状态」,优先评估依赖注入方案;无法规避全局实例时再使用单例。
2 通用编码规范
- 禁止对外暴露构造函数,阻止外部直接实例化;
- 支持延迟初始化(懒加载):首次访问才创建实例;
- 并发环境保证线程安全,避免重复创建;
- 统一提供 GetInstance() 作为唯一访问入口;
- 尽量区分:饿汉式(程序启动初始化) / 懒汉式(首次访问初始化);
- 不鼓励强制销毁接口;业务进程生命周期内实例唯一。
3 Go 标准实现
Go 业界标准方案:sync.Once 实现线程安全懒汉单例(推荐)
不推荐:裸 sync.Mutex 双重检查、饿汉静态变量(灵活性差)
package main
import (
"fmt"
"sync"
)
// Singleton 单例业务对象
type ConfigManager struct {
AppName string
Debug bool
}
var (
instance *ConfigManager
once sync.Once
)
// GetInstance 唯一访问入口,懒加载、并发安全
func GetInstance() *ConfigManager {
once.Do(func() {
// 初始化逻辑,仅执行一次
instance = &ConfigManager{
AppName: "singleton-demo",
Debug: true,
}
})
return instance
}
func main() {
c1 := GetInstance()
c2 := GetInstance()
fmt.Printf("c1 == c2 ? %t\n", c1 == c2) // c1 == c2 ? true
}
Go 两种方案选型说明
1. 懒汉式 sync.Once(工程首选)
首次调用才初始化;适合初始化较重、不一定会被使用的组件。
2. 饿汉式
var instance = &ConfigManager{AppName: "demo"}
func GetInstance() *ConfigManager { return instance }
程序启动直接创建;适合初始化极简、必然使用的组件。
Go 专项约束
- 禁止使用 sync.Mutex 手写双重检查锁定,sync.Once 是官方最优原语;
- 单例指针全局共享,结构体内部可变字段读写需要自行加锁;
- 尽量避免单例内部大量可变状态,降低并发 bug 风险。
4 Rust 标准实现
Rust 版本演进对照:
lazy_static! → once_cell::Lazy → 标准库 LazyLock(Rust ≥1.80)
方案 A:Rust ≥1.80,标准库 LazyLock(新项目首选,零第三方依赖)
use std::sync::LazyLock;
// 单例对象
#[derive(Debug)]
pub struct ConfigManager {
pub app_name: String,
pub debug: bool,
}
// 全局唯一实例,懒加载
pub static INSTANCE: LazyLock<ConfigManager> = LazyLock::new(|| ConfigManager {
app_name: "singleton-demo".to_string(),
debug: true,
});
fn main() {
let c1 = &*INSTANCE;
let c2 = &*INSTANCE;
println!("{:p}", c1);
println!("{:p}", c2);
}
方案 B:Rust 1.70 ~ 1.79 使用 OnceLock
适合:需要运行中途手动初始化的场景
use std::sync::OnceLock;
#[derive(Debug)]
pub struct ConfigManager {
pub app_name: String,
}
pub static INSTANCE: OnceLock<ConfigManager> = OnceLock::new();
// 访问入口
pub fn get_instance() -> &'static ConfigManager {
INSTANCE.get_or_init(|| ConfigManager {
app_name: "demo".to_string(),
})
}
fn main() {
println!("{:?}", get_instance());
}
方案 C:历史项目 once_cell /lazy_static(存量老代码)
Rust 专项约束
- LazyLock 生成 &'static T,不可变引用;
如果需要内部修改状态,内部搭配 Mutex/RwLock 内部可变性; - 不要尝试 static mut,极易触发 UB;
- 单例尽量存放无状态数据;可变数据务必同步原语保护。
示例:带内部可变状态单例
use std::sync::{LazyLock, Mutex};
pub static COUNTER: LazyLock<Mutex<u64>> = LazyLock::new(|| Mutex::new(0));
5 Go / Rust 单例模式核心对比
| 对比维度 | Go | Rust |
|---|---|---|
| 标准懒加载原语 | sync.Once | LazyLock(≥1.80) / OnceLock(≥1.70) |
| 变量载体 | 包级 var 指针 | static 静态变量 |
| 初始化时机 | GetInstance()首次调用 | 首次解引用 &*INSTANCE |
| 可变状态处理 | 结构体字段自行加锁 | 依赖 Mutex/RwLock 内部可变性 |
| 第三方依赖 | 无 | ≥1.80 无依赖;旧版本可选用 once_cell |
| 典型写法 | 封装 GetInstance() 函数 | 直接使用static INSTANCE |
6 最佳实践与常见误区
6.1 常见误区
- 盲目到处使用单例;优先依赖注入,单例是兜底方案;
- Go 手写双重检查锁替代 sync.Once;
- Rust 使用 static mut 实现可变单例,产生未定义行为;
- 在单例中大量存放可变全局状态,导致并发难以调试;
- 将单例作为万能方案,忽略单元测试 Mock 困难问题。
6.2 最佳实践
- 懒加载优先:组件不一定启用时,使用延迟初始化;
- 高并发场景严格使用语言官方同步原语,不要自己造同步逻辑;
- 单例内部尽量无状态;必须可变时,配套锁保护;
- 存量代码:
- Go:统一收敛到 sync.Once;
- Rust:新版本逐步从 lazy_static/once_cell 迁移至标准库 LazyLock;
- 边界场景:需要动态延迟初始化、等待配置文件加载,Rust 使用 OnceLock。

浙公网安备 33010602011771号