在学习rust的异步编程,阅读async-book遇到不太理解的内容,跟着bilibili上的视频去学习了一遍,因为缺少实际应用,有些点还是迷糊的,在这里简短的做个笔记,方便回头再学习
一、一些基本概念
主要是为了明确一些概念,只有概念理解了,才能更好的运用。最后再推荐一个关于讲解rust异步的文档。
1. 什么是进程、线程、协程
进程的概念就相当于我们的程序,运行起来就会开启一个进程,它是资源分配的最小单位。
线程相当于是进程资源的划分,就像公路被划分成多条车道,它是CPU调度执行的最小单位。
协程,这个概念之前一直不明白,查了查deepseek,如果是前端,举个例子Promise的使用,就是协程,在一个线程里,能中断A的执行,去执行B。协程是可以由异步,以及线程来实现的,它可以是多线程的。
2. 什么是并发,并行
并发:就是CPU在多个程序间切换执行,例如Promise.all([A,B]),执行A如果A遇到阻塞,再去执行B,如果B又阻塞,就去再执行A。如果cpu是单核的,即使是多线程运行,它也只能在一个时刻执行一条指令。
并行:它是具有条件的,必须是多核才能并行,就是同一时间,多个程序同时执行。
总结:
- 并发是软件概念(可以模拟"同时")
- 并行是硬件概念(需要物理上的多个执行单元)
- 单核CPU只能实现并发,无法实现真正的并行
- 多核CPU既可以实现并发,也可以实现并行
3. 并发和异步的区别
异步:是一种并发编程模型,允许在等待 I/O 操作时让出 CPU
原先自己的理解是异步就是并发,查了查deepseek举了下面的例子:
总结起来就是,并发可以由异步来实现,但两者不相等。
// 并发但不异步的例子 - 多线程阻塞IO
use std::thread;
use std::time::Duration;
fn main() {
// 并发:两个线程同时运行
let handle1 = thread::spawn(|| {
std::thread::sleep(Duration::from_secs(2)); // 阻塞
println!("线程1完成");
});
let handle2 = thread::spawn(|| {
std::thread::sleep(Duration::from_secs(1)); // 阻塞
println!("线程2完成");
});
handle1.join().unwrap();
handle2.join().unwrap();
}
// 异步但不并发的例子 - 顺序执行异步任务
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// 异步但顺序执行(不并发)
async_task1().await; // 等待第一个完成后再执行第二个
async_task2().await;
}
async fn async_task1() {
sleep(Duration::from_secs(2)).await; // 非阻塞等待
println!("任务1完成");
}
async fn async_task2() {
sleep(Duration::from_secs(1)).await;
println!("任务2完成");
}
// 并发 + 异步的例子
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// 同时启动多个异步任务(并发 + 异步)
let task1 = async_task1();
let task2 = async_task2();
// 并发执行异步任务
tokio::join!(task1, task2);
}
async fn async_task1() {
sleep(Duration::from_secs(2)).await;
println!("任务1完成");
}
async fn async_task2() {
sleep(Duration::from_secs(1)).await;
println!("任务2完成");
}
4. 并发、线程、协程、异步关系图
并发(Concurrency)
├── 进程(Processes) // 重量级,操作系统隔离
├── 线程(Threads) // 中等重量,共享内存
└── 协程(Coroutines) // 轻量级,用户态调度
├── 异步/await(Rust、JS、Python)
├── Goroutines(Go)
├── 生成器(Generators)
└── 绿色线程(Green Threads)
4. rust中的异步
Rust 的异步既可以是单线程的,也可以是多线程的,这取决于使用的运行时(Runtime)和执行器的配置。
单线程运行时
// 使用 tokio 的单线程运行时
#[tokio::main(flavor = "current_thread")]
async fn main() {
// 所有任务都在单个线程上执行
let task1 = async { /* ... */ };
let task2 = async { /* ... */ };
tokio::join!(task1, task2);
}
多线程运行时(默认)
// 使用 tokio 的多线程运行时(默认)
#[tokio::main]
async fn main() {
// 任务可能在不同的线程上执行
let task1 = async { /* ... */ };
let task2 = async { /* ... */ };
tokio::join!(task1, task2);
}
二、理解Send和Sync
-
Send:通过Send允许在线程间转移所有权
A type is Send if it is safe to send it to another thread.(如果可以安全地将类型发送到另一个线程,则类型为 Send。)
-
Sync:允许多线程访问,是通过不可变引用来进行访问的
A type is Sync if it is safe to share between threads (如果在线程之间共享是安全的,则类型为 Sync)
-
如果一个类型完全由 Send 或 Sync 类型组成,那么它就是 Send 或 Sync
示例:如下面的例子,因为Rc是没有实现send和sync的,而tikio::spawn入参需要时send的,所以会报错
use std::rc::Rc;
#[derive(Debug)]
struct Handler;
#[tokio::main]
async fn main(){
let handler = Rc::new(Handler);
let handler_cloned = Rc::clone(&handler);
tokio::spawn(process(handler_cloned));
}
async fn process(handler: Rc<Handler>){}
修复,将Rc更改为Arc
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug)]
struct Handler;
#[tokio::main]
async fn main(){
let handler = Rc::new(Handler);
let handler_cloned = Rc::clone(&handler);
tokio::spawn(process(handler_cloned));
}
async fn process(handler: Rc<Handler>){}
比较容易犯错的地方,和上面第三点有关,虽然Handler是Send的但内部字段是非Send的,所以也就成了非Send的数据
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug)]
struct Handler{
rc:Rc<u32>,
}
#[tokio::main]
async fn main(){
let handler = Arc::new(Handler:{Rc::new(1)});
let handler_cloned = Arc::clone(&handler);
tokio::spawn(process(handler_cloned));
}
async fn process(handler: Arc<Handler>){}
三、Rust Future异步理解
Rust只提供Future机制,以及相应的async/await语法支持。而async runtime留给社区开发,什么是运行时?,可以查看下官方文档,又是一个嘴里只会说,但没有实质理解的概念。
Future与Async runtime的交互
- Future 中的 poll 方法、Context、Poll enum是和 AsyncRumtime 交互的关键
- Async runtime 通过 poll 方法让 Future 执行
- Future 通过 Poll 告诉 Async runtime 执行情况
- Future 通过 Context 告诉 Asyncruntime 自己已就绪
use tokio::time::sleep;
use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
thread,
time::Duration,
};
#[tokio::main]
async fn main() {
SleepFuture::new(Duration::from_secs(2)).await;
}
struct SleepFuture {
duration: Duration,
state:State,
}
struct State{
waker:Option<Waker>,
inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
Init,
Sleeping,
Done,
}
impl SleepFuture {
fn new(duration: Duration) -> Self {
Self {
duration,
state:State{
waker:None,
inner_state:InnerState::Init,
}
}
}
}
impl Future for SleepFuture{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.state.inner_state == InnerState::Done{
return Poll::Ready(())
}
if self.state.inner_state == InnerState::Init{
self.state.waker = Some(cx.waker().clone());
self.state.inner_state = InnerState::Sleeping;
thread::spawn(move || {
thread::sleep(self.duration);
self.state.inner_state = InnerState::Done;
if let Some(waker) = self.state.waker.take(){
// 通知异步运行时 重新再调用poll
waker.wake();
}
});
}
Poll::Pending
}
}
修复
use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
thread,
time::Duration,
};
#[tokio::main]
async fn main() {
println!("start in main");
SleepFuture::new(Duration::from_secs(2)).await;
println!("end in main");
}
struct SleepFuture {
duration: Duration,
state:Arc<Mutex<State>>,
}
struct State{
waker:Option<Waker>,
inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
Init,
Sleeping,
Done,
}
impl SleepFuture {
fn new(duration: Duration) -> Self {
Self {
duration,
state:Arc::new(Mutex::new(State{
waker:None,
inner_state:InnerState::Init,
}))
}
}
}
impl Future for SleepFuture{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut guard = self.state.lock().unwrap();
println!("polling...");
if guard.inner_state == InnerState::Done{
return Poll::Ready(())
}
if guard.inner_state == InnerState::Init{
guard.waker = Some(cx.waker().clone());
guard.inner_state = InnerState::Sleeping;
let duration = self.duration;
let state_cloned = Arc::clone(&self.state);
thread::spawn(move || {
println!("start sleeping...");
thread::sleep(duration);
let mut guard = state_cloned.lock().unwrap();
guard.inner_state = InnerState::Done;
if let Some(waker) = guard.waker.take(){
waker.wake_by_ref();
}
println!("wake up");
});
}
guard.waker = Some(cx.waker().clone());
println!("pending...");
Poll::Pending
}
}
四、async/await机制理解
Rust async/await是语法糖,真正执行的还是Future+Poll
async方法生成一个Future,await则是进行poll轮询,可以将async理解成一个Future以及一个状态机
//Cargo.toml
[dependencies]
tokio = { version = "1.48.0", features = ["full"] }
//lib.rs:
use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
thread,
time::Duration,
};
pub async fn sleep(duration: Duration){
SleepFuture::new(duration).await;
}
pub struct SleepFuture {
duration: Duration,
state:Arc<Mutex<State>>,
}
struct State{
waker:Option<Waker>,
inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
Init,
Sleeping,
Done,
}
impl SleepFuture {
pub fn new(duration: Duration) -> Self {
Self {
duration,
state:Arc::new(Mutex::new(State{
waker:None,
inner_state:InnerState::Init,
}))
}
}
}
impl Future for SleepFuture{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut guard = self.state.lock().unwrap();
println!("polling...");
if guard.inner_state == InnerState::Done{
return Poll::Ready(())
}
if guard.inner_state == InnerState::Init{
guard.waker = Some(cx.waker().clone());
guard.inner_state = InnerState::Sleeping;
let duration = self.duration;
let state_cloned = Arc::clone(&self.state);
thread::spawn(move || {
println!("start sleeping...");
thread::sleep(duration);
let mut guard = state_cloned.lock().unwrap();
guard.inner_state = InnerState::Done;
if let Some(waker) = guard.waker.take(){
waker.wake_by_ref();
}
println!("wake up");
});
}
guard.waker = Some(cx.waker().clone());
println!("pending...");
Poll::Pending
}
}
// main.rs
//demo_test为创建的项目名称
use demo_test::sleep;
use std::time::Duration;
#[tokio::main]
async fn main() {
let v = vec![1,2,3];
let s = String::from("hello");
foo(v,s).await;
//FooFut::new(v,s).await;
}
// 这里执行的顺序大概是,先执行第一个println!
// 然后执行第一个sleep,该函数第一次状态可能是Poll::Pending
// 那么让出线程控制权
// 等第一个sleep结束后,再进入foo方法
// 开始执行第二个println!以及第二个sleep
// 然后又是先让出控制权,等Poll::Ready时,再进入foo函数
// 最后执行 返回42
async fn foo(v:Vec<u32>,s:String)->u32{
println!("foo-v:{:?}",v);
sleep(Duration::from_secs(2)).await;
println!("foo-s:{}",s);
sleep(Duration::from_secs(2)).await;
42
}
// async fn foo函数运行时大概逻辑
struct FooFut{
state:FooFutState,
v:Vec<u32>,
s:String,
}
impl FooFut{
fn new(v:Vec<u32>,s:String)->Self{
Self{
state:FooFutState::Init,
v,
s,
}
}
}
enum FooFutState{
Init,
Sleeping1(SleepFuture),
Sleeping2(SleepFuture),
Done,
}
impl Future for FooFut{
type Output = u32;
fn poll(mut self:Pin<&mut Self>,cx:&mut Context<'_>) -> Poll<Self::Output>{
loop{
match self.as_mut().get_mut().state{
FooFutState::Init=>{
println!("foo-v:{:?}",self.v);
let fut1 = SleepFuture::new(Duration::from_secs(2));
self.as_mut().get_mut().state = FooFutState::Sleeping1(fut1);
}
FooFutState::Sleeping1(ref mut fut1)=>{
match Pin::new(fut1).poll(cx){
Poll::Ready(_)=>{
println!("foo-s:{}",self.s);
let fut2 = SleepFuture::new(Duration::from_secs(2));
self.as_mut().get_mut().state = FooFutState::Sleeping2(fut2);
}
Poll::Pending=>{
return Poll::Pending;
}
}
}
FooFutState::Sleeping2(ref mut fut2)=>{
match Pin::new(fut2).poll(cx){
Poll::Ready(_)=>{
self.as_mut().get_mut().state = FooFutState::Done;
}
Poll::Pending=>{
return Poll::Pending;
}
}
}
FooFutState::Done=>{
return Poll::Ready(42);
}
}
}
}
}
五、Pin机制理解
为什么需要PIn?
自引用结构
可以由手工的自引用结构
async生成的匿名Future也可能是自引用结构
自引用结构的内存不能被移动
否则将带来内存安全问题
Pin的语义
Pin主结构内存不被改变
Pin<P\<T>>确保的是T被pin住,而非P
如 对于Pin<&mut T>,pin住的是T,而不是&mut T
如 对于Pin<Box\<T>>, pin住的是T,而不是Box\<T>
Unpin和!Unpin
Pin与Unpin不是一对,Unpin和!Unpin才是一对
可以把Pin理解成动词,Unpin理解成形容词
Pin<P\<T>>对于T:Unpin不起作用,因为T是Unpinable,被Pin也没有意义
在使用Pin需要注意点是,当Pin住了元素,还要在源数据结构中添加_marker: PhantomPinned,来告诉数据不能再使用unPin的方法,否则比如通过get_mut仍能获取到unpin的数据,这样pin就没有意义了