From e4d4714242e3054a3cf10e548346a29c10256412 Mon Sep 17 00:00:00 2001 From: zrll <46812903+zrll12@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:45:15 +0800 Subject: [PATCH] Update global-variable.md --- src/advance/global-variable.md | 82 ++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/src/advance/global-variable.md b/src/advance/global-variable.md index da8b742a..65da063c 100644 --- a/src/advance/global-variable.md +++ b/src/advance/global-variable.md @@ -313,22 +313,27 @@ fn main() { } ``` -## 标准库中的 OnceCell +## 标准库中的 Once 和 Lazy -在 `Rust` 标准库中提供了实验性的 `lazy::OnceCell` 和 `lazy::SyncOnceCell` (在 `Rust` -1.70.0版本及以上的标准库中,替换为稳定的 `cell::OnceCell` 和 `sync::OnceLock` )两种 -`Cell` ,前者用于单线程,后者用于多线程,它们用来存储堆上的信息,并且具有最 -多只能赋值一次的特性。 如实现一个多线程的日志组件 `Logger`: +在 Rust 1.70.0 版本之前,标准库通过实验性 API 提供 `lazy::OnceCell` 和 `lazy::SyncOnceCell` 来实现单次初始化容器,这时,懒初始化主要依靠 `lazy_static` 。 +随着 `cell::OnceCell` 和 `sync::OnceLock` 在 1.70.0 中稳定和 `LazyCell` 和 `LazyLock` 在 1.80.0 中稳定,只使用标准库实现懒加载成为可能。 +这4个容器可以这样区分: -```rust -// 低于Rust 1.70版本中, OnceCell 和 SyncOnceCell 的API为实验性的 , -// 需启用特性 `#![feature(once_cell)]`。 -#![feature(once_cell)] -use std::{lazy::SyncOnceCell, thread}; +| | 单线程 | 多线程 | +|------|----------|----------| +| Once | OnceCell | OnceLock | +| Lazy | LazyCell | LazyLock | + +其中,`Lazy` 会自动按需加载内容,让代码更简洁,更人性化,而 `Once` 则可以手动指定初始化的时机或使用不同的方法初始化,更强大。 +`Cell` 的实现更简单,效率也更高,但是他并不保证线程安全,而 `Lock` 通过内部同步机制实现了线程安全。 + +#### Once 使用方法 -// Rust 1.70版本以上, -// use std::{sync::OnceLock, thread}; +下面,我们先看一下 `Once` 的使用方法: + +```rust +use std::{sync::OnceLock, thread}; fn main() { // 子线程中调用 @@ -350,11 +355,7 @@ fn main() { #[derive(Debug)] struct Logger; -// 低于Rust 1.70版本 -static LOGGER: SyncOnceCell = SyncOnceCell::new(); - -// Rust 1.70版本以上 -// static LOGGER: OnceLock = OnceLock::new(); +static LOGGER: OnceLock = OnceLock::new(); impl Logger { fn global() -> &'static Logger { @@ -371,7 +372,8 @@ impl Logger { } ``` -以上代码我们声明了一个 `global()` 关联函数,并在其内部调用 `get_or_init` 进行初始化 `Logger`,之后在不同线程上多次调用 `Logger::global()` 获取其实例: +以上代码我们声明了一个 `global()` 关联函数,并在其内部调用 `get_or_init` 进行初始化 `Logger`,之后在不同线程上多次调用 `Logger::global()` 获取其实例。 +由于需要在多个线程中使用,所以我们使用了 `OnceLock`。以下是输出: ```console Logger is being created... @@ -382,7 +384,49 @@ thread message 可以看到,`Logger is being created...` 在多个线程中使用也只被打印了一次。 -特别注意,目前 `OnceCell` 和 `SyncOnceCell` API 暂未稳定,需启用特性 `#![feature(once_cell)]`。 +#### Lazy 使用方法 + +上述例子如果使用 `LazyLock` 来实现,则可以变得更加简洁: + +```rust +use std::{sync::LazyLock, thread}; + +fn main() { + // 子线程中调用 + let handle = thread::spawn(|| { + let logger = &LOGGER; + logger.log("thread message".to_string()); + }); + + // 主线程调用 + let logger = &LOGGER; + logger.log("some message".to_string()); + + let logger2 = &LOGGER; + logger2.log("other message".to_string()); + + handle.join().unwrap(); +} + +#[derive(Debug)] +struct Logger; + +static LOGGER: LazyLock = LazyLock::new(Logger::new); + +impl Logger { + fn new() -> Logger { + println!("Logger is being created..."); + Logger + } + + fn log(&self, message: String) { + println!("{}", message) + } +} +``` + +以上代码中,我们使用 `LazyLock::new` 方法直接对全局变量 `LOGGER` 进行赋值,并传入一个初始化函数。 +使用的时候,可以直接使用对全局变量的引用。在获取引用的时候, `LazyLock` 会自动检查初始化的状态并进行初始化。 ## 总结