← 中文资源

简体中文版本

OnceLock 惰性初始化

用 std 的 OnceLock 在首次使用时恰好初始化一次共享配置,初始化后无需加锁。

阅读英文原文

功能说明

该示例用 OnceLock 实现进程级、线程安全的惰性初始化。配置在第一次访问时读取环境变量并缓存,之后所有调用直接返回同一引用。与运行时加锁不同,初始化完成后读取是普通内存访问,没有锁开销,适合读多写零的全局配置或注册表。

关键代码

静态 CONFIG 是 OnceLock,get_or_init 无论多少线程并发调用,闭包只会执行一次,返回对所有调用一致的可变借用转静态引用。try_override 用 set 在尚未初始化时写入,已初始化则返回错误而不是覆盖,使初始化顺序问题显式暴露。测试验证多次调用返回同一内容,说明初始化只发生一次。

使用注意

取回的是 &’static 引用,因此写入的值必须拥有且长期有效,不能借用短生命周期数据。set 与 get_or_init 竞争时只有先到者成功,调用方需处理“已被初始化”的错误,否则会误以为覆盖生效。该原语适合不可变配置;若配置需要变更,应改用 RwLock 或原子替换。注意它在跨 .so 或动态库时仍是每进程一份,不是跨进程共享。

代码示例

use std::sync::OnceLock;

static CONFIG: OnceLock<String> = OnceLock::new();

fn load_config() -> String {
    std::env::var("APP_CONFIG").unwrap_or_else(|_| "default".to_string())
}

pub fn config() -> &'static str {
    CONFIG.get_or_init(load_config)
}

pub fn try_override(value: &str) -> Result<(), &'static str> {
    CONFIG.set(value.to_string()).map_err(|_| "config already initialized")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn init_is_idempotent() {
        let first = config();
        let second = config();
        assert_eq!(first, second);
    }
}