面试题答案
一键面试1. 任务分发系统设计与实现
- 引入依赖
在
Cargo.toml
中添加tokio
和arc - mutex
相关依赖:[dependencies] tokio = { version = "1", features = ["full"] } arc - mutex = "1"
- 线程池初始化
use std::sync::{Arc, Mutex}; use tokio::runtime::Runtime; fn init_thread_pool() -> Runtime { Runtime::new().expect("Failed to initialize Tokio runtime") }
- 任务提交与执行逻辑
struct TaskDispatcher { thread_pool: Runtime, shared_resource: Arc<Mutex<()>>, } impl TaskDispatcher { fn new() -> Self { let thread_pool = init_thread_pool(); let shared_resource = Arc::new(Mutex::new(())); TaskDispatcher { thread_pool, shared_resource, } } fn submit_task<F>(&self, task: F) where F: FnOnce() + Send + 'static, { let shared_resource = self.shared_resource.clone(); self.thread_pool.spawn(async move { let _lock = shared_resource.lock().expect("Failed to lock shared resource"); task(); }); } }
- 资源竞争与死锁处理
- 资源竞争:通过
Arc<Mutex<()>>
来保护共享资源。Mutex
提供了互斥锁机制,在任务执行前通过lock
方法获取锁,这样同一时间只有一个任务能访问共享资源,从而避免资源竞争。 - 死锁处理:为了避免死锁,在设计任务逻辑时,确保任务获取锁的顺序一致。例如,如果多个任务需要获取多个锁,统一按照某个顺序(如锁的内存地址从小到大)获取锁。同时,在获取锁时设置合理的超时时间,
tokio
中的Mutex
支持异步获取锁并设置超时,这样可以在一定时间内获取不到锁时放弃并处理相应的错误,防止死锁。
- 资源竞争:通过
2. 选择FnOnce trait的原因
- FnOnce:
- 唯一性:
FnOnce
表示闭包可以被调用一次。在任务分发系统中,每个任务通常只需要执行一次,所以FnOnce
语义上更符合这种场景。例如,某个任务是进行资源的初始化操作,执行一次后就完成了其使命,后续不需要再次调用。 - 所有权转移:
FnOnce
闭包在调用时会转移所有权。这在涉及到一些有状态的闭包时很有用,当任务执行时,闭包的状态可以被消耗掉,不会产生重复使用的问题。例如,一个闭包持有某个文件句柄,在执行任务时将文件句柄消耗掉进行文件操作,之后这个闭包不再持有文件句柄,避免了资源的重复操作或错误使用。
- 唯一性:
- 相比Fn和FnMut:
- Fn:
Fn
表示闭包可以被调用多次,并且不获取所有权,也不会修改自身状态。在任务分发系统中,大多数任务不需要重复执行,使用Fn
不符合任务执行一次的语义,而且Fn
不能消耗自身状态,对于一些需要消耗资源的任务不适用。 - FnMut:
FnMut
表示闭包可以被调用多次且可以修改自身状态,但同样不转移所有权。在任务分发场景下,任务通常执行一次且可能需要消耗自身资源,FnMut
的多次调用语义以及不转移所有权特性不符合需求。
- Fn:
完整示例代码如下:
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;
fn init_thread_pool() -> Runtime {
Runtime::new().expect("Failed to initialize Tokio runtime")
}
struct TaskDispatcher {
thread_pool: Runtime,
shared_resource: Arc<Mutex<()>>,
}
impl TaskDispatcher {
fn new() -> Self {
let thread_pool = init_thread_pool();
let shared_resource = Arc::new(Mutex::new(()));
TaskDispatcher {
thread_pool,
shared_resource,
}
}
fn submit_task<F>(&self, task: F)
where
F: FnOnce() + Send + 'static,
{
let shared_resource = self.shared_resource.clone();
self.thread_pool.spawn(async move {
let _lock = shared_resource.lock().expect("Failed to lock shared resource");
task();
});
}
}
fn main() {
let dispatcher = TaskDispatcher::new();
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
dispatcher.submit_task(move || {
let mut count = counter_clone.lock().unwrap();
*count += 1;
println!("Task executed, counter: {}", *count);
});
}