拓冰建站拓冰建站
首页 / 资讯中心 / 正文

如何用 tokio::sync::OnceCell 实现全局配置的异步一次性初始化

如何用 tokio::sync::OnceCell 实现全局配置的异步一次性初始化【免费下载链接】tokioA runtime for writing reliable asynchronous applications with Rust. Provides I/O, networking, scheduling, timers, ...项目地址: https://gitcode.com/GitHub_Trending/to/tokio在 Rust 异步服务里全局配置数据库连接参数、特征开关等往往需要一段异步过程才能拿到读文件、请求远端并且要求整个进程只初始化一次、之后所有任务共享只读访问。tokio::sync::OnceCell就是为此设计的源文档把它描述为thread-safe cell that can be written to only oncetypically used for global variables that need to be initialized once on first use并且与标准库OnceLock的关键区别是——初始化过程本身可以是异步的。本文解决的任务用OnceCell声明一个全局配置通过get_or_init在首次访问时完成异步加载多个任务并发访问时保证只执行一次初始化并能核对行为是否符合预期。以下内容基于仓库中的 tokio/src/sync/once_cell.rs 文档注释和测试文件 tokio/tests/sync_once_cell.rs。前提条件一个 Rust 环境仓库 tokio/Cargo.toml 声明rust-version 1.71、edition 2021当前版本为 1.53.1以及一个能驱动异步代码的运行时。准备条件引入 tokio 并选择特性OnceCell在tokio::sync模块中对应sync特性文档示例里的#[tokio::main(flavor current_thread)]还需要rt和macros。两种写法[dependencies] # 简单做法full 一次打开所有常用特性 tokio { version 1.53.1, features [full] }如果不想全量引入按 tokio/Cargo.toml 的特性列表本文示例所需的最小组合是[dependencies] tokio { version 1.53.1, features [sync, rt, macros] }full与最小组合均包含sync、rt、macros差别只在是否同时启用fs、net、time等无关模块。默认特性为空default []不显式开启特性时OnceCell不可用。声明全局 OnceCell 并封装访问入口全局变量要用OnceCell::const_new()构造因为它是唯一能在static上下文中使用的构造函数文档说明Equivalent toOnceCell::new, except that it can be used in static variables。下面这段是源文档中的完整示例可以原样放入src/main.rs直接运行use tokio::sync::OnceCell; async fn some_computation() - u32 { 1 1 } static ONCE: OnceCellu32 OnceCell::const_new(); // 文档示例封装一个访问函数调用方拿到的是 static 引用 async fn get_global_integer() - static u32 { ONCE.get_or_init(|| async { 1 1 }).await } #[tokio::main(flavor current_thread)] async fn main() { let result get_global_integer().await; assert_eq!(*result, 2); }要点get_or_init(f)接收一个返回异步操作的闭包。若 cell 已有值直接返回T不会再执行f文档原文Gets the value currently in theOnceCell, or initialize it with the given asynchronous operation.返回值是T引用而非值因为 cell 初始化后不允许再变更。文档同时说明get_or_init在并发等待、取消/panic 重试等场景下的语义见下文多任务竞争一节。把文档示例换成配置场景时结构不变只替换类型和加载逻辑use tokio::sync::OnceCell; struct AppConfig { name: String, // 按实际配置项添加字段 } static CONFIG: OnceCellAppConfig OnceCell::const_new(); async fn load_config() - AppConfig { // 示例占位这里替换为真实的异步加载逻辑 // 例如读取配置文件、请求远端配置服务 AppConfig { name: default.to_owned() } } async fn get_config() - static AppConfig { CONFIG.get_or_init(load_config).await }load_config函数体必须替换为你自己的加载逻辑这是文中唯一需要你自行提供实现的地方。运行并验证最小示例cargo run成功条件即示例自带的断言assert_eq!(*result, 2)通过、程序正常退出说明const_new声明、get_or_init异步初始化和static封装三个环节都工作正常。这是文档示例中的断言值示例输出不是某个通用预期。多任务竞争时只初始化一次这是该场景的核心保证全部来自 once_cell.rs 中get_or_init的文档注释等待共享结果If some other task is currently working on initializing theOnceCell, this call will wait for that other task to finish, then return the value that the other task produced. 即后来的调用者不会启动第二次加载而是挂起等待并拿到先完成者的值。失败可重试If the provided operation is cancelled or panics, the initialization attempt is cancelled. If there are other tasks waiting for the value to be initialized, one of them will start another attempt at initializing the value. 初始化被select!取消或 panic 后等待队列中的其他任务会重新发起一次初始化。递归死锁This will deadlock ifftries to initialize the cell recursively. 初始化函数内部不能反过来调用同一个 cell 的get_or_init。仓库测试 tokio/tests/sync_once_cell.rs 用两个测试锁定了这些行为可作为你的行为核对基准get_or_init两个任务并发调用一个初始化函数立即返回5func1另一个睡 1ms 后返回10func2。测试断言两个任务拿到的值都等于5——两个调用方共享先完成者的结果10从未写入。get_or_init_panic一个任务的初始化函数func_panic睡 1ms 后 panic另一个任务的func1先完成。测试断言两个任务最终都拿到5验证了panic 后等待者重试的语义。你可以在本仓库检出中运行cargo test --test sync_once_cell复现上述验证。写自己的并发场景时可以按同样思路构造两个竞争的初始化函数并断言两者取到同一值。初始化可能失败改用 get_or_try_init如果加载过程本身可能失败配置文件缺失、远端返回错误用get_or_try_init它的初始化 future 返回ResultT, E调用结果是ResultT, E。文档语义与get_or_init相同只是额外一条If the provided operation returns an error, is cancelled or panics, the initialization attempt is cancelled. 也就是说某次尝试返回Err时值不会写入等待中的其他任务会重试。配置场景的写法static CONFIG: OnceCellAppConfig OnceCell::const_new(); // LoadError 为你的加载错误类型 async fn get_config() - Resultstatic AppConfig, LoadError { CONFIG.get_or_try_init(load_config).await }仓库测试get_or_try_init的行为对照任务 1 用返回Err(())的func_err任务 2 用返回Ok(10)的func_ok断言任务 1 得到Err任务 2 得到10——失败的尝试没有污染 cell成功的尝试完成了初始化。只读访问与同步写入get / initialized / setget_or_init之外还有几个日常会碰到的入口签名与语义均见 once_cell.rsinitialized() - bool判断是否已初始化。get() - OptionT未初始化时返回None测试get_uninit验证了这一点已初始化时返回Some(T)。get_mut() - Optionmut T需要可变引用时可安全修改值文档解释依据是the mutable borrow statically guarantees no other references exist。set(value) - Result(), SetErrorT同步写入仅在 cell 为空时成功。失败分两种由SetError的两个变体区分并带有对应的判断辅助方法AlreadyInitializedError(T)is_already_init_err()cell 已经有值。测试set_twice中第一次set(5)成功第二次set(6)返回该错误。InitializingError(T)is_initializing_err()另一个任务正在通过get_or_init初始化中。测试set_while_initializing验证了这种情况返回InitializingError且最终值仍由get_or_init的初始化方写入。错误变体携带的值就是未能写入的那个value方便上层做后续处理。如果值在编译期或运行初期就已确定不需要异步初始化可以直接预置OnceCell::new_with(Some(value))、const上下文的OnceCell::const_new_with(value)或OnceCell::from(value)文档示例OnceCell::from(2)后get().unwrap()等于2。需要把值取走而非借用时用take()或into_inner()取走后 cell 变空返回OptionT测试drop_into_inner验证了取出后原 cell 不再执行析构。边界与限制Send/Sync 边界见 once_cell.rs 末尾的 unsafe impl 注释OnceCellT仅在T: Send Sync时实现Sync因为get会跨线程交出共享引用仅在T: Send时实现Send。配置类型里若含Rc、裸引用等编译期会直接报错。tracing 特性下的盲区启用不稳定的tracing特性时用const_new/const_new_with创建的OnceCell不会被 instrument在 tokio-console 中不可见文档明确建议if that is needed 时改用OnceCell::new/new_with。对不启用该特性的普通项目无影响。递归初始化会死锁前文已述写load_config时避免在其中再访问同一个 cell。set与异步初始化并存时会互相排斥异步初始化进行中调用set得到InitializingError而不是阻塞等待。如果初始化逻辑是异步的统一走get_or_init/get_or_try_init不要用set去补写。参考tokio/src/sync/once_cell.rsOnceCell与SetError的完整 API 文档tokio/tests/sync_once_cell.rs竞争、panic 重试、set冲突等行为的仓库测试tokio/src/sync/mod.rssync模块中OnceCell的导出位置tokio/Cargo.toml特性列表与版本信息【免费下载链接】tokioA runtime for writing reliable asynchronous applications with Rust. Provides I/O, networking, scheduling, timers, ...项目地址: https://gitcode.com/GitHub_Trending/to/tokio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门