Once、OnceCell、OnceLock:Rust 一次性初始化终极指南

Rust单例模式:OnceLock的使用指南 告别单例模式的锁焦虑!Rust的`OnceLock`像轻巧的守门人,用原子操作解决初始化竞态,无需`Arc`的沉重。更惊艳的是编译期的`Send+Sync`机制:当多个线程通过`get_instance()`访问数据库连接时,Rust编译器会严格检查对象是否满足线程安全约束——就像给每个共享对象颁发"跨线程通行证"。对比Java/C#的运行时锁,Rust在编译期就捕获线程安全问题,零成本抽象+错误前置的设计,让机器替你熬夜排查错误。单例模式从未如此优雅安全! 阅读详情

Once、OnceCell、OnceLock:Rust 一次性初始化终极指南

在 Rust 开发中,我们经常会遇到一次性初始化的场景:比如全局配置加载、单例实例创建、资源初始化(如数据库连接、日志器)等。Rust 标准库提供了 Once、OnceCell 和 OnceLock 来解决这个问题。本文将从应用场景、核心 API、实战示例等维度,带你彻底搞懂三者的用法与选型。

为什么需要专门的一次性初始化工具?

在没有这些工具之前,我们实现一次性初始化可能会面临诸多问题:

  • 用普通变量标记初始化状态,无法保证多线程安全,容易出现数据竞争;
  • 用 Mutex<Option<T>> 包裹,每次访问都需要加锁,性能开销较大,且可能出现锁中毒;
  • 用社区第三方库,如 lazy_static,需要引入外部依赖,且灵活性不足。

而 Once、OnceCell、OnceLock 作为标准库原生工具,既保证了线程安全(按需),又兼顾了性能,还能根据场景灵活选择,彻底解决了上述痛点。

Once / OnceCell / OnceLock 详解

三者的核心区别在于:是否存储值和是否线程安全。

Once:最基础的一次性执行(不存储值)

Once 位于 std::sync::Once,是最基础的一次性初始化工具。它不存储任何值,仅保证一段代码只被执行一次,无论多少线程同时调用,最终只会有一个线程执行目标代码,其他线程会阻塞等待直到执行完成。

use std::sync::{Once, OnceLock};

// 全局 Once 实例,用于控制日志器初始化
static INIT_LOGGER: Once = Once::new();

// 模拟日志器
struct Logger;

impl Logger {
    fn init() -> Self {
        println!("日志器初始化中...");
        // 模拟耗时操作
        std::thread::sleep(std::time::Duration::from_millis(100));
        Logger
    }
}

// 全局日志器实例
static LOGGER: OnceLock<Logger> = OnceLock::new();

fn get_logger() -> &'static Logger {
    INIT_LOGGER.call_once(|| {
        // 用 OnceLock 的 set 方法安全存储初始化后的实例
        let _ = LOGGER.set(Logger::init());
    });
    // Once 确保初始化完成,因此 unwrap 安全
    LOGGER.get().unwrap()
}

fn main() {
    // 多线程同时获取日志器,验证初始化只执行一次
    let handles: Vec<_> = (0..5).map(|_| {
        std::thread::spawn(|| {
            let logger = get_logger();
            println!("线程 {:?} 获取到日志器", std::thread::current().id());
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }
}

Once 是线程安全的,底层通过原子操作实现,所以无需额外加锁。但是缺点也很明显,那就是不存储值,需要配合 OnceLock 或其他安全存储的工具来管理状态。相比直接使用 OnceCell/OnceLock 不够便捷。

OnceCell:单线程的一次性存储(不保证线程安全)

OnceCell 位于 std::cell::OnceCell,它不仅能保证一次性初始化,还能存储一个值,无需手动管理状态。但需要注意的是 OnceCell 不保证线程安全,仅适用于单线程场景。

下面的示例是单线程场景下,用 OnceCell 存储配置,避免重复加载:

use std::cell::OnceCell;

// 模拟配置结构体
#[derive(Debug)]
struct Config {
    database_url: String,
    timeout: u64,
}

impl Config {
    fn load() -> Self {
        println!("加载配置中...");
        std::thread::sleep(std::time::Duration::from_millis(100));
        Config {
            database_url: "mysql://root:123456@localhost:3306/db".to_string(),
            timeout: 30,
        }
    }
}

fn main() {
    let mut config_cell = OnceCell::new();

    // 第一次获取:未初始化,执行 load 并存储
    let config1 = config_cell.get_or_init(Config::load);
    println!("第一次获取配置: {:?}", config1);

    // 第二次获取:已初始化,直接返回
    let config2 = config_cell.get_or_init(Config::load);
    println!("第二次获取配置: {:?}", config2);

    // 尝试重新设置值:失败,返回 Err
    let result = config_cell.set(Config {
        database_url: "postgres://user:pass@localhost:5432/db".to_string(),
        timeout: 60,
    });
    println!("重新设置配置: {:?}", result); // Err(Config { ... })

    // 可变引用修改(需持有 mut 引用)
    if let Some(mut config) = config_cell.get_mut() {
        config.timeout = 45;
    }
    println!("修改后配置: {:?}", config_cell.get().unwrap());
}

OnceLock:多线程的一次性存储(线程安全)

OnceLock 位于 std::sync::OnceLock,是 OnceCell 的线程安全版本。它继承了 OnceCell 的一次性存储特性,同时保证了多线程环境下的安全访问,底层结合了 Once 的线程同步机制和 UnsafeCell 的值存储能力,且不会像 Mutex 那样出现锁中毒问题。

在多线程场景下,OnceLock 是最常用的一次性初始化工具,尤其适合创建全局单例、共享资源等场景。

下面的示例是用 OnceLock 实现线程安全的单例,确保实例只被创建一次:

use std::sync::OnceLock;

// 模拟 HTTP 客户端
struct HttpClient {
    base_url: String,
    timeout: u64,
}

impl HttpClient {
    fn new(base_url: String, timeout: u64) -> Self {
        println!("HTTP 客户端初始化中...");
        std::thread::sleep(std::time::Duration::from_millis(200));
        HttpClient { base_url, timeout }
    }

    fn get(&self, path: &str) -> String {
        format!("GET {}{}", self.base_url, path)
    }
}

// 全局单例 HTTP 客户端
static HTTP_CLIENT: OnceLock<HttpClient> = OnceLock::new();

// 获取全局 HTTP 客户端
fn get_http_client() -> &'static HttpClient {
    HTTP_CLIENT.get_or_init(|| {
        HttpClient::new("https://api.example.com".to_string(), 30)
    })
}

fn main() {
    // 多线程同时获取客户端,验证初始化只执行一次
    let handles: Vec<_> = (0..10).map(|i| {
        std::thread::spawn(move || {
            let client = get_http_client();
            let response = client.get(&format!("/users/{}", i));
            println!("线程 {:?} 发送请求: {}", std::thread::current().id(), response);
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }
}

OnceLock 实现了 Sync + Send 特征,所以可以安全地跨线程共享,适合作为全局静态变量。当调用 get_or_init 时,只有一个线程会执行闭包,其他线程会阻塞等待,避免重复初始化。OnceLock 与 Mutex<Option<T>> 相比,OnceLock 无需每次访问都加锁,初始化完成后读取操作无锁,性能更优,且不会因 panic 导致锁中毒。

快速选型

特性OnceOnceCellOnceLock
是否存储值否(仅执行代码)是是
线程安全是(仅保证代码执行一次)否(单线程专用)是(多线程专用)
适用场景无值存储的一次性初始化(如全局资源启动)单线程延迟初始化、局部一次性存储多线程延迟初始化、全局单例、共享资源
性能极高(仅原子操作,无值存储开销)高(无线程同步开销)较高(初始化时有同步开销,读取无锁)
是否需要 unsafe是(需手动管理存储值)否否
核心优势极简、轻量,专注于一次性执行单线程场景下便捷、高效,无需同步线程安全,原生支持全局静态变量,无锁读取

常见坑

  • 将 OnceCell 用于多线程场景:OnceCell 不实现 Sync 特征,跨线程共享会触发编译错误,此时应使用 OnceLock;
  • 过度依赖 Once:Once 不存储值,手动管理状态容易出错,有存储需求时优先用 OnceCell/OnceLock;
  • 忽略 set 方法的返回值:set 失败时会返回传入的值,若不处理可能导致值丢失;
  • 在 get_or_init 闭包中 panic:闭包 panic 后,Once/OnceCell/OnceLock 会标记为已执行,后续无法再初始化,需避免闭包 panic。

总结

根据实际场景(是否多线程、是否需要存储值)选择合适的工具,让代码更简洁、高效、安全。

rust学习Cell、RefCell、OnceCell Cell、RefCell 和 OnceCell 类型的值可以通过共享引用(即公共 &T 类型)进行mutate,而大多数 Rust 类型只能通过唯一 (&mut T) 引用(unique reference)进行mutate。这些cell类型提供了“内部可变性,interior mutability”(通过 &T 可变),与表现出“继承可变性”(仅通过 &mut T 可变)的典型 Rust 类型形成鲜明对比。Cell类型分为三种类型:Cell、RefCell 和 OnceCell。 阅读详情

相关推荐

Once_cell:Rust库,用于单个分配单元和无宏的惰性静态

Once_cell:Rust库,用于单个分配单元和无宏的惰性静态

使用 Rust OnceLock 实现高效的单次初始化

OnceLock是 Rust 提供的一个非常高效且易于使用的工具,它帮助我们在多线程环境中实现了惰性初始化。它的线程安全性和简洁的 API,使得在需要保证某些资源只初始化一次的场景中非常适用。通过合理使用OnceLock,我们可以避免重复计算和初始化的开销,提高程序的性能和可维护性。

咚为 1368

海洋科学—物理海洋学 第五章 海洋环流

本章是基于《海洋科学导论》,但里面涉及到的重要的公式推导是专门拎出来推的,主要借鉴董昌明老师的《物理海洋学导论》,其中有一些多出来的推导,会在更新完整个物理海洋学之后重新拿出来一一讲解,最后开一个番外篇

qq_58518586的博客 1万+

RUST的Once源代码分析

分析RUST的Once实现源代码。从源代码可以看到,RUST与C在某些实现上一脉相承

weixin_45429253的博客 979

uni-app蓝牙MTU设置失效探因:从20字节限制到跨设备兼容性实战

本文深入探讨了uni-app蓝牙开发中MTU设置失效的常见问题,特别是20字节限制的成因及解决方案。通过分析设备兼容性、蓝牙协议栈特性和系统版本差异,提供了智能延迟策略、设备特征检测和渐进式MTU提升等实战方案,帮助开发者优化蓝牙数据传输效率。

weixin_29289821的博客 289

从零构建XVC协议栈:FPGA+W5300的跨界调试架构实战

本文详细介绍了如何从零构建XVC协议栈,通过FPGA与W5300以太网控制器的创新组合实现跨界调试架构。重点解析了JTAG时序逆向、TCP/IP协议栈集成与XVC协议实现,提供了硬件设计优化和性能加速技术,为嵌入式开发者和FPGA工程师提供实用的远程调试解决方案。

raspberrypi5的博客 251

rust原子和锁 第二章翻译

Rust Atomics and Locks Chapter 2. Atomics 中文翻译

qq_43413205的博客 608

Vue3 + OpenLayers 开发指南(一) - 环境搭建与基础配置

本文详细介绍了如何从零开始搭建Vue3与OpenLayers的集成开发环境。通过项目初始化、核心依赖安装、目录结构规划及基础配置,手把手引导开发者创建一个结构清晰、可维护的现代化WebGIS应用骨架,为后续添加地图图层和交互功能奠定坚实基础。

weixin_28049429的博客 342

OnceCell和OnceLock的介绍

OnceLock 是一个用于实现懒加载、并发安全的数据结构,它是 Rust 标准库中的一个实验性类型,目前处于 unstable 状态。和 std::sync::Once 类似,OnceLock 可以确保一个变量只被初始化一次,但是它可以在多线程环境下进行安全访问。以下是一个使用 OnceLock 的示例:});println!("{winner:?} wins!");在这个例子中,我们使用 OnceLock 来创建一个名为 WINNER 的变量,并在多线程环境下使用它。

weixin_42048993的博客 1002

3D Slicer-最强大的开源医学图像分割工具简要概述

3D Slicer 是一个免费的开源软件平台,用于医学、生物医学和其他 3D 图像的可视化、处理、分割、配准和分析。读/写DICOM图像和多种其他格式;三维图像、多边形网格和体积渲染的交互式可视化;手动编辑和标记图像;使用刚性和非刚性算法融合和共同配准数据;图像自动分割;跟踪图像引导程序的设备。所有的功能模块可在工具栏里找到,提供搜索,下拉选择,左右切换等功能。3D slicer的大概情况就介绍到这里。它的功能非常强大,也没有必要说掌握每个功能的使用。

Tina姐的博客 2万+

告别裸机:在FreeRTOS上为STM32移植SOEM 1.4.0的完整指南

本文详细介绍了在FreeRTOS环境下为STM32H7系列芯片移植SOEM 1.4.0的完整指南,涵盖环境准备、任务架构设计、OSAL层适配及网络驱动优化等关键步骤。通过EtherCAT主站解决方案的实现,帮助开发者构建稳定可靠的工业自动化系统,提升实时通信性能。

weixin_33582311的博客 364

DiscImageStudio配套工具全家福:FourLevelDither、DvdGrayProbe与BluRayByteProbe

DiscImageStudio 是一款开源的光盘盘面可见图像生成与刻录工具,它能把普通图片转换成符合 CD / DVD / 单层蓝光物理层约束的数据,刻录后在光盘表面呈现肉眼可见的图案。除了主程序,项目还自带一套"配套工具全家福":黑白抖动工具 FourLevelDither、DVD 灰阶测试盘生成器 DvdGrayProbe 和蓝光字节密度分析器 BluRayByteProbe。它们分别负责**

gitblog_00279的博客 483

AUTOSAR从入门到精通-中间件通信DDS(一)

汽车开放系统架构(automotive open system architecture,autosar)是由全球各大汽车oem、零部件供应商、汽车电子软件公司联合建立的一套标准协议。该协议规范了一个符合汽车电子软件开发的、开放的以及标准化的软件架构。最初的autosar规范的制定只考虑传统汽车电子电器架构,基于硬件算力有限场景,提供确定的功能。

getusushu的博客 1871

Rust 延迟初始化解析:OnceCell 与 OnceLock 的原理与实战

在系统编程中,延迟初始化是一种重要的资源管理策略,它把耗时的对象创建推迟到首次使用时,从而加快启动速度并降低内存占用。Rust 凭借所有权与生命周期机制,对全局可变状态有着严格约束,static 变量必须在编译期确定,导致运行时配置、数据库连接池等场景难以直接落地。OnceCell 与 OnceLock 正是为此设计的一次性初始化容器:前者面向单线程,后者面向多线程,通过原子状态机与 release/acquire 内存序保证线程安全,让“只初始化一次”成为类型安全的能力。这一模式广泛应用于环境变量读取、全

weixin_33393017的博客 287

车载软件刷写工具vFlash --- 自动化接口(Automation API)应用简介

车载软件刷写工具vFlash --- 自动化接口(Automation API)应用简介

Soly_kun的博客 362

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Rust OnceCell 深度解析:延迟初始化的优雅解决方案

在 Rust 编程中,我们经常遇到需要延迟初始化全局变量或静态变量的场景。传统的做法可能涉及复杂的宏或Mutex包装,但自 Rust 1.70 起,OnceCell和OnceLock已经稳定进入标准库,为我们提供了更加优雅和高效的解决方案。本文将深入探讨OnceCell的使用场景、实现原理和最佳实践。OnceCell是一个只能写入一次的容器类型。一旦被初始化,它将永久保存该值,后续的写入操作将失败。这种特性使其特别适合用于延迟初始化和全局状态管理。- 单线程版本- 线程安全版本Go 的。

techdashen的博客 710

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

once_cell最佳实践:10个技巧提升你的Rust代码质量

once_cell是一个强大的Rust库,提供了无需宏的单赋值单元格和延迟静态变量功能。它通过OnceCell和SyncOnceCell等类型,帮助开发者实现高效的资源初始化和状态管理,是提升Rust代码质量的重要工具。 [![once_cell Logo](https://raw.gitcode.com/gh_mirrors/on/once_cell/raw/80fe900b21f6d76c1

gitblog_00986的博客 464

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

darbuka_arabic_tts_dataset应用实战:训练阿拉伯语TTS、微调ASR与反欺骗模型的3条路径

Darbuka Arabic(darbuka_arabic_tts_dataset)是一个约 1,944 小时、94 万条短语音切片的阿拉伯语开源数据集,内置三路 ASR 转写文本与音频质量评分,是训练阿拉伯语 TTS、微调 ASR 和构建反欺骗(anti-spoofing)模型的即用型语料库,支持 WebDataset 流式读取,无需下载全部 885 GB 即可开工。 ## 数据集一览:先看有

gitblog_07622的博客 367

oh-my-openagent 中的 Rust 并发原语决策树:从 tokio 通道、parking_lot 锁到 loom 模型检查的完整实践指南

本指南以 oh-my-openagent 开源仓库中 `shared-skills` 技能包所内置的 Rust 并发参考文档为核心骨架,系统讲解通道(channel)、锁(lock)、原子操作(atomic)与 loom 模型检查器的选型与用法。读完本文,你将掌握一套"通道优先、锁其次、原子最后"的并发原语决策方法,能依据状态形态正确选型、写出可编译且运行时不死锁的并发代码,并用 loom 与 m

gitblog_01112的博客 393

Rust中const与static的区别:内存模型、使用场景与最佳实践

在编程语言中,常量与全局变量是管理程序状态的基础概念。常量通常指编译时确定且不可变的值,而全局变量则在程序整个生命周期内存在固定内存地址。从原理上看,常量通过编译时值替换实现零开销访问,全局变量则在数据段分配内存并通过地址引用。这种差异在Rust语言中尤为关键,因为它直接关系到内存安全与并发安全的技术价值。在实际应用场景中,编译时常量适合定义配置参数和数组大小,而全局只读数据则适用于查找表和共享配置。本文聚焦Rust的const与static关键字,深入解析两者的内存分配、线程安全机制,并提供全局可变状态的

weixin_33994429的博客 334

RustTraining C++ → Rust 语义深潜指南:四类强制转换、预处理器、模块可见性与 trait 边界的完整映射

> **导读**:本文基于 RustTraining 仓库中《Rust for C/C++ Programmers》一书的第 18 章([ch18-cpp-rust-semantic-deep-dives.md](https://link.gitcode.com/i/8a29627ac754edc7354d57bce71585e3)),系统讲解 C++ 中那些在 Rust 里没有 1:1 对应物的

gitblog_00182的博客 210

RUST编程学习-碎片3-sync

Rust的std::sync模块提供了多线程同步的核心工具,主要包括:原子类型(如AtomicBool、AtomicI32等)实现无锁并发;互斥锁(Mutex)和读写锁(RwLock)控制共享资源访问;屏障(Barrier)协调线程执行顺序;条件变量(Condvar)实现线程间事件通知;以及线程安全的引用计数(Arc)。这些工具通过原子操作和RAII机制保证线程安全,是Rust并发编程的基础组件,在需要线程同步的场景中被频繁使用。

Healerice的博客 343

Rust中const与static的本质区别:从编译期常量到全局变量

在编程语言中,常量与全局变量是两种基础且核心的概念,它们分别代表了不同的内存模型和生命周期管理方式。常量通常在编译期确定其值,作为字面量的别名被直接内联到使用位置,没有独立的内存地址;而全局变量则在程序整个生命周期内拥有固定的存储位置,支持获取引用和跨作用域共享。从技术价值角度看,正确区分这两者有助于编写出意图清晰、内存安全且易于优化的代码。在Rust语言中,这种区分尤为关键:const用于定义编译期常量表达式,适合替换魔数、定义数组长度等场景;static则用于声明具有'static生命周期的全局变量,适

weixin_33798152的博客 457

Rust RFC 2592 深度解读:标准库 Future、任务系统与 Waker API 的设计与稳定化

本篇技术指南基于 Rust RFC 仓库中的 [text/2592-futures.md](https://link.gitcode.com/i/f4d0d7637004ee7afc7ba5bacfdbda1b) 展开,系统讲解 Rust 将 `Future` trait 与整套任务(task)/轮询(poll)/唤醒(waker)机制纳入标准库的完整设计:包括 `Poll`、`Waker`、`R

gitblog_00924的博客 183

Rust 全局变量的最佳实践 lazy_static/OnceLock/Mutex/RwLock

在实际项目开发中,难免需要用到全局变量,比如全局配置信息,全局内存池等,此类数据结构可能在多处需要被使用,保存为全局变量可以很方便的进行修改与读取。在Rust中,如果只是读取静态变量是比较简单的,比如全局变量是一个usize或者& str等类型的值。如果全局变量是需要初始化产生的就比较复杂了,比如解析一个配置文件,然后把配置文件中的内容赋给全局变量。由于全局变量要被修改,这个全局变量得是可变的,也就是说产生了全局可变变量,而这种方式违反了Rust的设计原则。

笨笨D幸福 - 后花园 2408

Rust Once 、OnceLock、LazyLock 一次性初始化详解

本文详解Rust中一次性初始化工具:Once仅保证执行一次,需配合unsafe存储数据;OnceLock<T>安全存储懒加载全局数据;LazyLock<T>是更简洁的语法糖。通过对比和实战,指导如何按需选用,避免数据竞争并提升性能。

本博客分享软硬件一体化开发实战。硬件涵盖 PCB 设计、STM32 等 MCU 底层开发;软件包含 C++、Rust,Qt/QML、PyQt、Slint、TouchGFX、LVGL 各类 GUI 框架。方便软硬件开发者查阅学习。欢迎关注! 543
上一篇: Rust 1.95.0 正式发布:核心特性升级与生态完善,一文带你看完整更新
下一篇: Rust 并发同步之屏障(Barrier):让多线程步调一致
Rust研习社
博客等级 码龄11年 52粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值