面试题答案
一键面试资源释放及避免风险策略
- 自动内存管理:Rust的所有权和借用规则,以及RAII(Resource Acquisition Is Initialization)机制在资源释放上起着关键作用。当子线程结束(包括被取消)时,栈上变量的析构函数会自动被调用,从而释放它们所管理的资源。例如,
String
类型在析构时会释放其内部的堆内存。
fn main() {
let handle = std::thread::spawn(|| {
let s = String::from("hello");
// 线程结束时,`s` 的析构函数自动调用,释放堆内存
});
// 等待子线程结束
handle.join().unwrap();
}
Mutex
与Arc
:如果子线程需要共享资源,Mutex
(互斥锁)和Arc
(原子引用计数)是常用的工具。Arc
用于共享所有权,Mutex
用于控制对共享资源的访问。当子线程被取消时,Mutex
会确保在析构时资源的一致性。
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let handle = std::thread::spawn({
let data = Arc::clone(&data);
move || {
let mut data = data.lock().unwrap();
data.push(4);
// 线程结束时,`data` 析构,正确释放内存
}
});
handle.join().unwrap();
}
可能出现的内存安全风险场景及解决策略
- 悬空指针风险场景:假设子线程创建了一个堆上对象,并将指向该对象的指针传递给主线程。如果子线程在主线程使用该指针前被取消,主线程中的指针就会悬空。
// 错误示例
use std::thread;
fn main() {
let mut ptr: *mut i32 = std::ptr::null_mut();
let handle = thread::spawn(|| {
let data = Box::new(42);
ptr = Box::into_raw(data);
});
handle.join().unwrap();
// 此时 `ptr` 可能悬空,因为 `Box` 在子线程结束时已析构
if!ptr.is_null() {
unsafe {
println!("{}", *ptr);
}
}
}
解决策略:使用 Arc
和 Weak
指针。Arc
用于强引用,保持对象存活,Weak
指针用于弱引用,不影响对象的生命周期。当强引用计数为0(如子线程结束)时,Weak
指针可以检测到对象是否已被释放,避免悬空指针。
use std::sync::{Arc, Weak};
fn main() {
let data_arc: Arc<i32> = Arc::new(42);
let data_weak: Weak<i32> = Arc::downgrade(&data_arc);
let handle = std::thread::spawn({
let local_arc = Arc::clone(&data_arc);
move || {
// 子线程处理逻辑
}
});
handle.join().unwrap();
if let Some(data) = data_weak.upgrade() {
println!("{}", data);
} else {
println!("对象已被释放");
}
}
- 内存泄漏风险场景:如果子线程中存在手动内存分配(使用
unsafe
代码)且在取消时没有正确释放,就会导致内存泄漏。
// 错误示例
use std::thread;
fn main() {
let handle = thread::spawn(|| {
let ptr = unsafe { std::alloc::alloc(std::alloc::Layout::new::<i32>()) };
// 子线程结束时未释放 `ptr`,导致内存泄漏
});
handle.join().unwrap();
}
解决策略:将手动分配的内存封装在实现了 Drop
特征的结构体中,利用RAII机制确保内存正确释放。
use std::alloc::{alloc, dealloc, Layout};
use std::mem;
struct ManualAlloc {
ptr: *mut i32,
}
impl Drop for ManualAlloc {
fn drop(&mut self) {
if!self.ptr.is_null() {
unsafe {
dealloc(self.ptr as *mut u8, Layout::new::<i32>());
}
}
}
}
fn main() {
let handle = std::thread::spawn(|| {
let layout = Layout::new::<i32>();
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
panic!("内存分配失败");
}
let manual_alloc = ManualAlloc { ptr: ptr as *mut i32 };
// 线程结束时,`manual_alloc` 析构,释放内存
});
handle.join().unwrap();
}