ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

100-exercises-to-learn-rust:理解 Rust 内存泄漏——用 `Box::leak` 与 `Vec::leak` 跨越 `‘static` 生命周期

100-exercises-to-learn-rust:理解 Rust 内存泄漏——用 `Box::leak` 与 `Vec::leak` 跨越 `‘static` 生命周期 示例工程教程【免费下载链接】100-exercises-to-learn-rustA self-paced course to learn Rust, one exercise at a time.项目地址https://gitcode.com/GitHub_Trending/10/100-exercises-to-learn-rust点击查看免费下载在 Rust 的多线程编程中std::thread::spawn要求传入的闭包满足static生命周期否则借用检查器会以 argument requires thatvis borrowed forstatic详见 07_threads/02_static为由拒绝编译。除了把数据按值移入线程外还有一种绕过借用约束的经典手段主动泄漏内存。本文以 100-exercises-to-learn-rust 仓库第 7 章第 3 节 Leaking data 为骨架结合 03_leak 练习源码 与其测试用例系统讲解Box::leak/Vec::leak的原理、危险边界OOM与 OS 回收机制并给出何时可以放心泄漏的工程判断标准。泄漏问题的根源spawn 线程的static约束std::thread::spawn创建的线程是**脱离detached**的它可能在父线程退出之后继续运行直至整个进程结束。因此Rust 编译器要求传给spawn的闭包必须满足static约束——闭包捕获的值要么是自有的owned要么是引用但该引用在整个程序运行期间都有效pub fn spawnF, T(f: F) - JoinHandleT where F: FnOnce() - T Send static, T: Send static { // [..] }如果闭包捕获的是一个会在线程运行期间被释放的堆内存引用就会产生经典的use-after-free释放后使用漏洞。这正是 02_static 中sum练习遇到的编译错误v在函数返回时被 drop而线程仍然持有指向其内部的切片引用。核心方案用Box::leak获得static引用处理堆分配数据时你可以告诉 Rust这块内存永远不会被回收即故意泄漏leak内存从而拿到一个static引用。标准库的Box::leak正是为此设计// 在堆上分配一个 u32用 Box 包裹 let x Box::new(41u32); // 告诉 Rust 永远不会释放这块堆内存 // 从而拿到一个 static 引用 let static_ref: static mut u32 Box::leak(x);注意Box::leak返回的是static mut T——不仅生命周期延长到static还保留了可变性。这就让把大块堆数据切分后分发给多个线程成为可能无需复制数据。练习场景Vec::leak切分堆内存并行求和仓库中的 03_leak 练习 完整呈现了这个思路的实战形态。练习要求见 src/lib.rs给定一个Veci32泄漏其堆内存分配将得到的静态切片切成两半在两个独立线程中分别对两半求和。这正是 02_static 练习 中禁止额外分配内存约束的延续上一个练习用static数组满足static这里则需要把运行期的堆数据静态化。关键工具是Vec::leak它可以一步把VecT变成static mut [T]并顺便丢弃 Vec 的栈上元数据与容量信息只保留堆内存本身use std::thread; pub fn sum(v: Veci32) - i32 { // 泄漏堆内存获得 static 切片 let static_slice: static mut [i32] v.leak(); let mid static_slice.len() / 2; let left static_slice[..mid]; let right static_slice[mid..]; let left_handle thread::spawn(move || left.iter().sum::i32()); let right_handle thread::spawn(move || right.iter().sum::i32()); left_handle.join().unwrap() right_handle.join().unwrap() }测试验证练习自带的测试lib.rs 的 tests 模块覆盖了空向量、单元素、5/9/10 个元素的求和结果sum(vec![]) 0sum(vec![1]) 1sum(vec![1, 2, 3, 4, 5]) 15sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) 45sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 55空切片情况下mid 0两个线程各处理空切片求和自然为 0——Vec::leak对空 Vec 同样成立不会产生空指针问题。该练习的 Cargo.toml 仅依赖标准库edition 2021无需任何外部 crate 即可cargo test验证。内存泄漏是进程级的警惕 OOM泄漏数据是危险的如果持续泄漏进程最终会耗尽内存并以out-of-memoryOOM错误崩溃。原文档给出了一个触发函数// 如果让它一直运行最终会耗尽全部可用内存 fn oom_trigger() { loop { let v: Vecusize Vec::with_capacity(1024); v.leak(); } }Vec::with_capacity(1024)一次性分配一块 1024 个usize的堆空间随后leak让这块内存永远无法被释放循环每次迭代又新增一块内存占用单调递增直到触发系统 OOM。为什么泄漏不等于永远丢失OS 的进程级回收与直觉相反经leak泄漏的内存并非真正被遗忘。操作系统能够把每一块内存区域映射到负责它的进程当进程退出时操作系统会自动回收该进程的全部内存。所以让 OS 去处理let the OS deal with it在特定场景下是完全合理的内存管理策略。工程判断什么场景下故意泄漏是合理的原文档给出两条判断标准满足其一即可考虑使用泄漏方案需要泄漏的内存总量有界 / 提前可知例如只泄漏固定数量、固定大小的数据如上述练习中每个Vec只泄漏一次总量受输入规模约束泄漏总量可控不会无限增长。进程生命周期短确信进程在耗尽可用内存之前就会退出例如 CLI 工具、批处理任务、测试程序等短暂进程。对于长生命周期的服务进程比如仓库后续章节打造的 多线程票务服务端以及 通道版客户端-服务器架构内存是持续累积的核心资源应当避免任何形式的无界泄漏——这正是后续章节引入 scoped threads、channels、Arc等更安全方案的原因。对比与演进scoped threads 是更优雅的替代值得强调的是泄漏只是让引用满足static的一种手段并非唯一手段也未必是最佳手段。本书下一节 Scoped threads 提供了更安全的替代方案std::thread::scope创建的线程会在 scope 结束时自动 join因此可以安全借用非static的环境数据let v vec![1, 2, 3]; let midpoint v.len() / 2; std::thread::scope(|scope| { scope.spawn(|| { let first v[..midpoint]; println!(Heres the first half of v: {first:?}); }); scope.spawn(|| { let second v[midpoint..]; println!(Heres the second half of v: {second:?}); }); }); println!(Heres v: {v:?});scope版本既不拷贝数据、也不泄漏内存还能在 scope 返回后继续使用v。它和Box::leak/Vec::leak形成了鲜明的对照当你需要一块永久有效的堆数据时选泄漏当线程生命周期可以收束在某个作用域内时scoped threads 是零代价且零风险的答案。小结围绕 Leaking data 一文可以提炼出以下要点spawn要求闭包满足static这是避免 use-after-free 的编译期保障Box::leak以及Vec::leak通过放弃回收堆内存把普通引用升级为static引用适合把堆数据切分后送入多个线程泄漏的内存并非永久丢失进程退出时由操作系统统一回收无界泄漏会触发 OOM只有在泄漏量有界或进程短命时才可接受让 OS 兜底的策略当线程能被限定在某个作用域内时优先考虑std::thread::scope它是泄漏方案的更安全替代。理解这些边界是写出既通过编译又不会在生产环境吃光内存的并发 Rust 代码的第一步。下一步可以继续阅读本书后续章节Channels、Locks 与 Send/Sync 语义逐步构建完整的并发模型。赞分享示例工程教程【免费下载链接】100-exercises-to-learn-rustA self-paced course to learn Rust, one exercise at a time.项目地址https://gitcode.com/GitHub_Trending/10/100-exercises-to-learn-rust点击查看免费下载相关推荐100-exercises-to-learn-rust深入理解 Rust 生命周期Lifetimes与 IntoIterator for TicketStore100 exercises to learn rust深入理解 Rust 生命周期Lifetimes与 IntoIterator for TicketS示例工程教程深入 Rust 并发基石static 生命周期与 thread::spawn 的借用约束100-exercises-to-learn-rust深入 Rust 并发基石 static 生命周期与 thread::spawn 的借用约束100 exercises to learn rust st示例工程教程Rust 生命周期实践深入理解 static 生命周期Rust 生命周期实践深入理解 static 生命周期 什么是 static 生命周期 在 Rust 中 static 是一个特殊的生命周期标记表示文档教程示例工程上一篇Edgartools批量处理技巧高效下载并分析数千份SEC文件下一篇claude-seo 的 Google API 认证完全指南从 API Key、Service Account 到 GA4 的 8 步配置与源码级验证创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表