面试题答案
一键面试利用 async/await
进行性能优化
任务优先级调度
- 使用优先级队列:在Rust中,可以使用
PriorityQueue
库(如pqrs
)来管理任务。为每个任务分配一个优先级,在任务调度时,优先从队列中取出高优先级任务执行。
use pqrs::PriorityQueue;
let mut tasks = PriorityQueue::new();
tasks.push(Task { priority: 1, data: "High - priority task" }, 1);
tasks.push(Task { priority: 2, data: "Low - priority task" }, 2);
while let Some((task, _)) = tasks.pop() {
// 执行任务
async {
// 任务逻辑
}.await;
}
- 自定义调度器:实现一个自定义的任务调度器,根据任务的优先级进行调度。可以结合
futures::executor::ThreadPool
,在提交任务时根据优先级进行分发。
use futures::executor::ThreadPool;
use std::sync::Arc;
struct PriorityScheduler {
high_priority_pool: Arc<ThreadPool>,
low_priority_pool: Arc<ThreadPool>,
}
impl PriorityScheduler {
fn new() -> Self {
PriorityScheduler {
high_priority_pool: Arc::new(ThreadPool::new().unwrap()),
low_priority_pool: Arc::new(ThreadPool::new().unwrap()),
}
}
fn schedule(&self, task: impl Future<Output = ()> + Send + 'static, priority: u8) {
match priority {
1 => self.high_priority_pool.spawn(task),
_ => self.low_priority_pool.spawn(task),
};
}
}
资源复用
- 连接池:对于数据库连接、网络连接等资源,使用连接池进行复用。在Rust中,可以使用
r2d2
库来创建连接池。
use r2d2::Pool;
use r2d2_postgres::PostgresConnectionManager;
use postgres::Client;
type PostgresPool = Pool<PostgresConnectionManager>;
let manager = PostgresConnectionManager::new("postgres://user:password@localhost/mydb", Default::default()).unwrap();
let pool = Pool::builder().build(manager).unwrap();
async fn query_database(pool: &PostgresPool) {
let conn = pool.get().await.unwrap();
let rows = conn.query("SELECT * FROM users", &[]).await.unwrap();
// 处理查询结果
}
- 线程本地存储:利用线程本地存储(
thread_local!
)来复用一些线程内的资源,避免每个任务都重新创建。例如,复用一个BufWriter
对象用于日志记录。
thread_local! {
static LOGGER: std::sync::Mutex<BufWriter<File>> = std::sync::Mutex::new(BufWriter::new(File::create("log.txt").unwrap()));
}
async fn log_message(message: &str) {
LOGGER.with(|logger| {
let mut lock = logger.lock().unwrap();
writeln!(lock, "{}", message).unwrap();
lock.flush().unwrap();
});
}
减少上下文切换
- 合理设置线程数:根据系统的CPU核心数和I/O负载,合理设置线程池的线程数量。使用
num_cpus
库获取CPU核心数,避免创建过多线程导致上下文切换开销过大。
use num_cpus;
let num_threads = num_cpus::get();
let pool = ThreadPool::new().unwrap();
- 使用异步I/O驱动:Rust的
tokio
库提供了高效的异步I/O驱动,使用tokio::fs::File
代替标准库的std::fs::File
进行文件I/O操作,减少因阻塞I/O导致的上下文切换。
use tokio::fs::File;
async fn read_file() {
let file = File::open("example.txt").await.unwrap();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await.unwrap();
// 处理文件内容
}
处理异步操作中的背压问题
设计模式
- 生产者 - 消费者模式:使用
mpsc
(多生产者 - 单消费者)或sync_channel
(同步通道)来控制数据的流动。生产者将数据发送到通道,消费者从通道中取出数据进行处理。如果通道已满,生产者会被阻塞,从而实现背压控制。
use tokio::sync::mpsc;
async fn producer(tx: mpsc::Sender<i32>) {
for i in 0..10 {
tx.send(i).await.unwrap();
}
}
async fn consumer(rx: mpsc::Receiver<i32>) {
while let Some(data) = rx.recv().await {
// 处理数据
}
}
#[tokio::main]
async fn main() {
let (tx, rx) = mpsc::channel(5);
tokio::spawn(producer(tx));
consumer(rx).await;
}
- 流量整形:通过限制数据的发送速率来处理背压。可以使用
ratelimit
库来实现流量整形。
use ratelimit::RateLimit;
let rate_limit = RateLimit::new(100, std::time::Duration::from_secs(1)); // 每秒最多发送100个数据
async fn send_data(tx: mpsc::Sender<i32>) {
for i in 0..1000 {
rate_limit.acquire().await;
tx.send(i).await.unwrap();
}
}
示例代码(结合 async/await
和背压处理)
use tokio::sync::mpsc;
use std::time::Duration;
async fn producer(tx: mpsc::Sender<i32>) {
for i in 0..100 {
tx.send(i).await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
async fn consumer(rx: mpsc::Receiver<i32>) {
while let Some(data) = rx.recv().await {
println!("Received: {}", data);
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
#[tokio::main]
async fn main() {
let (tx, rx) = mpsc::channel(10);
tokio::spawn(producer(tx));
consumer(rx).await;
}
在上述代码中,producer
以一定的速率向通道发送数据,consumer
从通道接收数据并处理。如果consumer
处理速度较慢,通道会满,producer
会被阻塞,从而实现背压处理。