如果你还在纠结这个问题,恭喜你,来对地方了!把下面这个sample code 的执行过程理解透彻了,那基本就明白异步到底是怎么回事儿了。今后写起async 就可以更加自信了,你说呢?
sample code
fn main() {
// Create the mini-tokio instance.
let mini_tokio = MiniTokio::new();
// Spawn the root task. All other tasks are spawned from the context of this
// root task. No work happens until `mini_tokio.run()` is called.
mini_tokio.spawn(async {
// Spawn a task
spawn(async {
// Wait for a little bit of time so that "world" is printed after
// "hello"
delay(Duration::from_millis(100)).await;
println!("world");
});
// Spawn a second task
spawn(async {
println!("hello");
});
// We haven't implemented executor shutdown, so force the process to exit.
delay(Duration::from_millis(200)).await;
std::process::exit(0);
});
// Start the mini-tokio executor loop. Scheduled tasks are received and
// executed.
mini_tokio.run();
}
上面这段sample code 来源于tokio tutorial 官方,完整代码可以参考这里。
第一印象
编译器会把上面的代码拆分成三个并发的子任务,供executor 调度
async task1{
delay(Duration::from_millis(100)).await;
println!("world");
}
async task2{
println!("hello");
}
async task0{
delay(Duration::from_millis(200)).await;
std::process::exit(0);
}
其中task1的阻塞过程
delay(Duration::from_millis(100)).await;
和task0 的阻塞过程
delay(Duration::from_millis(200)).await;
会分别打包进线程中去执行。
深入执行过程
1.生成task0 并append 到channel 供executor 调度
*此时channel 中有一个任务*
2.executor从channel 中取出并执行task0
2.1 生成task1 并append 到channel 供executor 调度
2.2生成task2 并append 到channel 供executor 调度
2.3 在task0中开辟一个线程thread0执行阻塞过程(线程thread0结束前将task0重新append 到channel)
3.executor从channel 中取出并执行task1
3.1 在task1 中开辟一个线程thread1执行阻塞过程(线程thread1结束前将task1重新append 到channel)
*此时channel 中有task2 一个任务*
4.executor从channel 中取出并执行task2
4.1 task2执行完毕,返回ready
*此时channel 中没有任务,executor 会等待任务的到来*
5.task1 的线程thread1阻塞过程完毕,将task1重新append 到channel
*此时channel 中有task1 一个任务*
6.executor从channel 中取出并执行task1
6.1 task1执行完毕,返回ready
*此时channel 中没有任务,executor 会等待任务的到来*
7.task0 的线程thread0阻塞过程完毕,将task0重新append 到channel
*此时channel 中有task0 一个任务*
8.executor从channel 中取出并执行task0
8.1 task0执行完毕,进程强制退出exit
底层逻辑
1.executor 维护着一个channel,这个channel 装着需要调度的task,它只负责从中channel receiver端取出 task 然后执行task.poll
2.task 维护着具体的Future 代码块和channel 的sender 端
2.1 spawn 的时候会把自己send 到channel 中供executor 调度
2.2 poll 的时候会把waker/contex 信息作为Future.poll 的参数传递过去
- Future.poll 执行的时候遇到阻塞(等待某个资源)就会新开辟一个线程去做,并在完成之前通过waker把当前task 重新send 到channel,供executor 重新调度
- executor 只有一个,task 可以有N个,而task 之间存在嵌套的关系
两个通讯工具
- channel
executor 与 task 之间的通讯工具,task 告诉executor 有任务需要调度
- waker
task 与 Future 之间的通讯工具,Future 告诉task 你可以继续执行了。这里的Future 可以理解为第三方的资源,通知caller 你可以用了!