Rust异步编程入门
现代应用程序在处理网络、文件系统或数据库时,越来越需要高性能。Rust中的异步编程允许高效处理数千个并发连接,而无需为每个任务创建线程。与多线程相比,异步代码占用更少内存,扩展性更好。
Rust提供了一种基于零成本抽象的独特异步方法。这意味着你只为你使用的功能付费——未使用的功能不会产生额外开销。在本指南中,我们将解析关键概念并编写实用示例,帮助你立即开始编写Rust异步代码。
async/await与Futures基础
什么是Future?
Future是一个trait,代表一个可能在将来完成的异步操作。在Rust中,Future是惰性的:除非被轮询(poll),否则它不会执行任何操作。核心思想是:我们不阻塞线程,而是注册一个任务,并在结果准备好时获取它。
use std::future::Future;use std::pin::Pin;use std::task::{Context, Poll};
struct MyFuture { ready: bool,}
impl Future for MyFuture { type Output = u32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> { if self.ready { Poll::Ready(42) } else { // 注册waker以便再次轮询 cx.waker().wake_by_ref(); Poll::Pending } }}async/await——语法糖
async关键字将函数或代码块转换为Future。await会暂停执行直到Future完成,并将控制权交还给运行时。
use tokio::time::{sleep, Duration};
async fn fetch_data() -> String { // 模拟网络请求 sleep(Duration::from_secs(2)).await; String::from("数据已获取")}
#[tokio::main]async fn main() { let result = fetch_data().await; println!("{}", result);}生命周期与借用检查器
Rust的一个特点是严格的借用控制。在异步代码中这一点尤为重要,因为Future的生命周期可能比借用的数据更长。使用async move来捕获所有权:
async fn process_data(data: Vec<u32>) -> u32 { data.iter().sum()}
#[tokio::main]async fn main() { let numbers = vec![1, 2, 3, 4, 5]; let sum = process_data(numbers).await; println!("总和: {}", sum);}Tokio实战
安装与配置
Tokio是Rust最流行的异步运行时。在Cargo.toml中添加:
[dependencies]tokio = { version = "1", features = ["full"] }创建异步TCP服务器
我们来看一个简单的回显服务器,它接受连接并将接收到的数据发送回去:
use tokio::net::TcpListener;use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let listener = TcpListener::bind("127.0.0.1:8080").await?; println!("服务器已在8080端口启动");
loop { let (mut socket, addr) = listener.accept().await?; println!("新连接来自: {}", addr);
tokio::spawn(async move { let mut buf = [0; 1024]; loop { let n = socket.read(&mut buf).await.unwrap(); if n == 0 { break; } // 连接已关闭 socket.write_all(&buf[..n]).await.unwra