异步是如何实现并发的呢

发布于:2022-12-11 ⋅ 阅读:(647) ⋅ 点赞:(0)

如果你还在纠结这个问题,恭喜你,来对地方了!把下面这个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  的参数传递过去
  1. Future.poll 执行的时候遇到阻塞(等待某个资源)就会新开辟一个线程去做,并在完成之前通过waker把当前task 重新send 到channel,供executor 重新调度
  2. executor 只有一个,task 可以有N个,而task 之间存在嵌套的关系

两个通讯工具

  • channel

executor 与 task 之间的通讯工具,task 告诉executor 有任务需要调度

  • waker

task 与 Future 之间的通讯工具,Future 告诉task 你可以继续执行了。这里的Future 可以理解为第三方的资源,通知caller 你可以用了!

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到