RustInPractice(19):Tokio 异步基础
async 不是“自动开线程”。Rust 的 async fn 返回 Future,Future 只有被 runtime 轮询时才推进;遇到 .await 无法继续时,它让出执行权,其他任务可以运行。 RustInPractice 第 19 篇。对应 demo:learning_guide 的 async 子命令、thread_pool。下一篇讨论有界并发与后台任务。 一、Future 是惰性的描述123async fn fetch_name() -> String { "Rust".to_string()} 调用 fetch_name() 得到的是 Future,不会立刻执行。需要在 async 上下文 .await,并由 Tokio runtime 等 executor 轮询: 12345#[tokio::main]async fn main() { let name = fetch_name().await; println!("{name}");...
RustInPractice(18):Send、Sync 与消息传递
当 Rust 拒绝把某个值送进线程时,它不是在“限制你”,而是在检查线程边界上的安全契约。Send 描述值能否转移给另一个线程,Sync 描述共享引用能否被多个线程安全访问。 RustInPractice 第 18 篇。前置:第 17 篇原生线程。下一篇进入 Tokio async。 一、两个 marker trait trait 含义 Send 类型的所有权可安全转移到另一个线程 Sync &T 可安全被多个线程共享 大多数普通值自动实现二者。Rc<T> 不是 Send,因为引用计数不是原子操作;Arc<T> 使用原子计数,因此可跨线程共享。 123let data = std::sync::Arc::new(vec![1, 2, 3]);let worker = std::sync::Arc::clone(&data);std::thread::spawn(move || println!("{:?}", worker)); 二、优先传递所有权和消息如果任务不必共同修改...
RustInPractice(17):原生线程与同步原语
Rust 消除了大量内存不安全并发,但不会自动让逻辑免于死锁、饥饿或错误同步。先理解 thread、Mutex、Condvar、Barrier 和 atomic 的职责,再决定是否需要 async。 RustInPractice 第 17 篇。对应 demo:thread_demos、sync_primitives。下一篇解释 Send、Sync 与消息传递。 一、线程必须被等待或明确放弃123let handle = std::thread::spawn(|| "worker done");let result = handle.join().expect("worker panicked");println!("{result}"); join 等待线程结束并取得结果。demo 使用 unwrap() 让学习输出简短;生产代码应决定如何处理线程 panic,而不是无条件崩溃。 二、Mutex 保护共享可变状态1234use std::sync::{Arc, Mutex}...
RustInPractice(16):测试设计,写出可信代码
cargo test 很容易运行,但真正的难点是选择输入和断言行为。测试不是把 demo 输出再打印一遍,而是为边界、失败路径和不变量建立自动化证据。 RustInPractice 第 16 篇。对应 demo:testing_demo,并为后续算法和服务文章建立测试标准。 一、单元测试验证一个行为1234#[test]fn divide_by_zero_is_an_error() { assert!(Calculator::divide(1.0, 0.0).is_err());} 测试名说清行为而非实现。#[cfg(test)] 模块只在测试时编译,适合测试私有实现细节;公开 API 的行为还应有 integration test 覆盖。 二、优先覆盖边界每个集合和算法都至少应考虑:空输入、单元素、重复值、最大最小值、无效格式和错误分支。例如排序必须覆盖空 slice;动态规划应拒绝负容量和长度不一致输入;字符串算法必须声明它返回字节偏移还是字符位置。 123456#[test]fn sort_handles_empty_input() ...
RustInPractice(15):集合选型,Vec、VecDeque 与 HashMap
先选对标准集合,再考虑自定义数据结构。Rust 标准库的 Vec、VecDeque、HashMap、BTreeMap 已覆盖绝大多数工程需求;手写链表和哈希表更适合作为理解所有权与复杂度的练习。 RustInPractice 第三季第 15 篇。对应 demo:stack_queue_list、hash_table。下一篇将这些容器放进可信的测试体系。 一、按访问模式选容器 需求 首选 原因 末尾追加、按下标访问 Vec<T> 连续内存,缓存友好 两端进出队 VecDeque<T> push_back / pop_front 高效 键值快速查找 HashMap<K, V> 平均常数时间访问 需要稳定有序遍历 BTreeMap<K, V> 按 key 排序 去重 HashSet<T> / BTreeSet<T> 只保存 key demo 的栈直接用 Vec:push 与 pop 都在末尾进行。队列则使用 VecDeque,避免从 Vec 头部删除时搬移所...
YOLO 目标检测实战(01):Docker GPU 环境搭建
目标:在隔离的 Docker 镜像里跑通 YOLO 所需的 CUDA / PyTorch / Ultralytics / TensorRT,并准备好权重与样例数据。这一步做扎实了,后面训练、导出、部署才不会被环境问题反复拖住。 这是「YOLO 目标检测实战」的第 01 篇。系列共 8 篇,完整目录见 00 · 系列导读。文中路径统一用容器内 /workspace 或项目相对路径。 一、为什么用 Docker 宿主机只保留驱动与 NVIDIA Container Toolkit,训练依赖全部锁在镜像里。 新架构(如 Blackwell)需要较新的 CUDA wheel(本实验用 CUDA 12.8 + cu128),镜像便于复现。 同一镜像既做训练,也起 FastAPI,避免「本机能跑、服务环境缺包」这种最耗时的问题。 二、仓库结构(相关部分)12345678├── docker/ # Dockerfile + compose + entrypoint├── scripts/ # 分步脚本├── api/...
多模态微调实战(02):用 Docker 固化 ms-swift 训练环境
微调最怕两件事:依赖打架 和 驱动 / CUDA 不匹配。这一篇把训练环境固定在镜像里,宿主机只负责三件事:显卡驱动、Docker、项目目录。目标是让训练环境一次搭好、随处复现。 这是「多模态微调实战」的第 02 篇。上一篇讲了路线图,本篇解决「环境从哪来、怎么复现」。 一、硬件与软件前提 项目 建议 GPU 单卡,显存约 16GB 或以上 驱动 能跑你选用的 CUDA 容器即可(较新的消费卡通常需要较新的 PyTorch) 系统 Linux(本文以 Ubuntu 为例) Docker 已安装,并配置好 NVIDIA Container Toolkit 磁盘 预留 50GB+(模型 + 全量数据 + checkpoint) 自检: 12nvidia-smidocker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu22.04 nvidia-smi 第二行能打印 GPU 信息,说明容器 GPU 通路正常。 二、项目目录约定把下面当作「项目根」: 1234567~/projects...
RustInPractice(14):泛型、Trait 与静态/动态分发
Rust 不以 class 继承为核心。它把数据放进 struct 或 enum,把可替换行为写成 trait,再根据场景选择泛型的静态分发或 trait object 的动态分发。结果是组合关系清楚,抽象成本也更可见。 这是 RustInPractice 第二季收官篇。对应 demo:oop_demo、pimpl_demo、design_patterns。下一季进入集合、测试与并发工程实践。 一、泛型消除重复123fn swap<T, U>(pair: (T, U)) -> (U, T) { (pair.1, pair.0)} T、U 是类型参数。编译器会为实际使用的具体类型生成对应实现,这通常称为单态化。泛型函数不必事先知道具体类型,只需声明它需要的能力。 二、trait 定义能力而非继承层级1234567891011trait Compress { fn compress(&self, data: &str) -> String;}struct Plain;impl Com...
RustInPractice(13):RefCell 与内部可变性
默认情况下,只有拿到 &mut T 才能修改 T。RefCell<T> 是一个刻意的例外:它允许通过 &self 修改内部值,但把借用规则从编译期挪到运行时检查。它很有用,也因此需要更克制地使用。 这是 RustInPractice 的第 13 篇。对应 demo:refcell_demo、design_patterns。下一篇用泛型与 trait 组织可组合行为。 一、内部可变性是什么?demo 的计数器方法接收 &self,却能增加内部数值: 1234567891011use std::cell::RefCell;struct Counter { value: RefCell<i32>,}impl Counter { fn increment(&self) { *self.value.borrow_mut() += 1; }} borrow() 返回只读 guard,borrow_mut() 返回可写 guard。gua...
RustInPractice(12):Rc、Arc 与 Weak
默认的唯一所有权最简单,但有些图结构、缓存和只读共享数据确实需要多个拥有者。Rust 不会隐式开启垃圾回收,而是要求你选择引用计数类型:单线程用 Rc<T>,跨线程用 Arc<T>,反向或可选关联用 Weak<T>。 这是 RustInPractice 的第 12 篇。对应 demo:rc_demo、weak_demo。下一篇:RefCell<T> 与内部可变性。 一、Rc<T>:单线程共享所有权123456use std::rc::Rc;let config = Rc::new(String::from("shared"));let first = Rc::clone(&config);let second = Rc::clone(&config);println!("{} {}", first, second); Rc::clone 不会深拷贝 String,只增加强引用计数。最后一个强引用离开作用域时,值被释放...















