





















无论是c/c++,还是rust开发,都无可避免地需要进行多并发与异步编程,接下来以问题的方式进行讲解。
MutexGuard(即锁)期间发生了 Panic(恐慌),Rust 会在线程栈展开时,自动将该 Mutex 标记为 Poisoned(已毒化) 状态。Mutex::lock() 时,不会直接成功,而是返回一个 Err(PoisonError)。catch_unwind)或跨线程通信时,意外读取到已经被破坏的、处于非安全状态的数据。let mut data = rust_mutex.lock().unwrap_or_else(|e| e.into_inner()); 强行获取内部数据。use std::sync::Mutex;
use std::thread;
fn main() {
// 一个维持着“总和等于10”这一不变量的结构
let mutex = std::sync::Arc::new(Mutex::new(vec![5, 5]));
let c_mutex = mutex.clone();
let handle = thread::spawn(move || {
let mut data = c_mutex.lock().unwrap();
data[0] = 10; // 不变量已被打破(此时总和为15)
panic!("Thread panicked while holding the lock!"); // 显式触发 Panic,引发栈展开
});
let _ = handle.join(); // 捕获子线程崩溃
// ❌ 另一个线程尝试获取锁,会返回 Err(PoisonError)
match mutex.lock() {
Ok(_) => println!("Lock acquired successfully."),
Err(poison_err) => {
// 术语:PoisonError
println!("Error: Mutex is poisoned! Inner data: {:?}", poison_err.into_inner());
}
}
}
tokio::select! 分支被放弃时会发生什么?Future 在被丢弃(Dropped)时,即使该 Future 还没有运行到 Poll::Ready 状态,它也不会丢失任何正在处理的数据或破坏底层资源状态,那么这个异步操作就是取消安全的(Cancellation-safe)。在linux c编程中,有一个可重入函数(同一个函数在被多次重叠调用(Overlapping Invocation)时是否安全。),
“可重入”描述的是函数/代码(Code)的属性;而 Rust 异步的核心是数据/状态机(State)。引发灾难的不是代码被并发执行,而是由于引用的数据(Future 实例)突然提前解构,导致状态不完整。
Future 实例实现的(即调用其 Drop 析构函数)。tokio::select! 的放弃机制与隐患tokio::select! 宏会同时并发轮询(Poll)多个 Future。一旦其中一个分支率先返回 Poll::Ready,select! 会立即丢弃(Drop)其他所有未完成的分支。Future 是非取消安全的(Cancellation-unsafe),那么它在过去几次被 poll 期间暂存在异步状态机内部(栈帧上)的数据和进度就会彻底蒸发。use tokio::net::TcpStream;
use tokio::io::AsyncReadExt;
async fn cancel_unsafe_demo(stream: &mut TcpStream) {
let mut buffer = [0u8; 1024];
tokio::select! {
// ❌ tokio::io::AsyncReadExt::read 是非取消安全的
// 如果这里读取了半包数据(例如4个字节),接着下面的超时分支触发了
res = stream.read(&mut buffer) => {
println!("Read {} bytes", res.unwrap());
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(10)) => {
// 超时触发,上面的 `stream.read` Future 会被直接 Drop。
// 此时,这 4 个字节的数据已经从操作系统的 TCP 缓冲区移走,但由于 Future 被销毁,
// 这部分数据彻底丢失在虚空中,后续再次读取将导致数据流损坏!
println!("Timeout triggered, read branch dropped!");
}
}
}
Pin 又是如何解决它的?Pin 和 Unpin 的救赎Pin<P> 是一个指针包装器。它的核心作用是将一个指针所指向的数据“钉”在内存的绝对位置上,确保该数据在生命周期结束前,绝不会再发生任何内存移动(Move)。Unpin(Auto Trait):绝大多数类型都默认实现了 Unpin,代表它们移动时很安全,不受 Pin 的约束。而异步生成器/编译后产生的 Future 状态机(内部由于暂存局部引用,本质就是自引用结构体)则显式地去除了 Unpin(即 impl !Unpin)。Pin<&mut Self>,Rust 编译器向异步运行时保证:这个 Future 的状态机在后续的轮询(Poll)过程中绝对不会被移动,从而允许在不安全的自引用结构上安全地构建高层异步流。use std::marker::PhantomPinned;
use std::pin::Pin;
// 术语:自引用结构体(包含 !Unpin 的非安全类型)
struct SelfRef {
value: String,
// 故意让内部指针指向自己的 value 字段
pointer_to_value: *const String,
_marker: PhantomPinned, // 术语:去除了 Unpin 特质(即 !Unpin),标记该类型不可移动
}
fn main() {
// 在栈上创建一个自引用结构
let mut obj = SelfRef {
value: String::from("Hello"),
pointer_to_value: std::ptr::null(),
_marker: PhantomPinned,
};
obj.pointer_to_value = &obj.value; // 让指针指向自身
// ❌ 错误做法:没有使用 Pin,直接在栈上发生了值移动(Move)
let moved_obj = obj;
// 术语:未定义行为(Undefined Behavior)
// 此时 moved_obj.pointer_to_value 依然指向原先 `obj` 的旧栈地址,
// 而旧地址已被标记为无效,访问它将导致灾难性的内存损坏!
unsafe {
println!("Old Address Pointer: {:?}", *moved_obj.pointer_to_value);
}
}
参考资料:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。