Tokio
基础概念
1. 同步 (Synchronous)
任务执行必须按顺序来,一个任务完成后,下一个任务才能开始。就像你去餐厅点餐,必须等上一个菜吃完 / 上完,才能点下一个。
2. 异步 (Asynchronous)
任务执行不需要等待上一个任务完成,发起任务后可以去做其他事,等任务完成后会收到 “通知”(回调 / 事件)。就像点餐时先点完所有菜,不用等上一个菜上,后厨同时准备,菜做好了服务员会喊你。
3. 并发 (Concurrency)
多个任务在同一时间段内交替执行(看似同时进行),但同一时刻只有一个任务在执行。就像你一边写代码,一边回微信,一边喝水 —— 其实是大脑快速切换,同一时刻只做一件事。
核心:任务切换(上下文切换),利用等待时间(比如 IO 等待)处理其他任务。
- 异步编程是实现并发的常见方式;
- 单核 CPU 只能实现并发,无法实现并行。
4. 并行 (Parallelism)
多个任务在同一时刻真正同时执行。就像你和同事一起写代码,你们俩同一时刻都在敲键盘 —— 需要多核 CPU / 多个进程支持。
- 概念划分
- 并发、并行,是逻辑结构的设计模式。
- 同步、异步,是逻辑调用方式。
- 并发、并行是异步的 2 种实现方式。
单线程异步
1 | #添加完整功能的tokio到项目中 |
1 | use tokio::runtime; |
等效于
1 | async fn hi() { |
注意
- 去除
flavor = "current_thread"即可变为多线程异步 async fn main() {}并非异步函数,通过安装cargo-expand可以了解到
1 |
|
多线程异步
函数解析
tokio::spawn()
示例代码
1 | let task = tokio::spawn(async move { |
tokio::spawn 接受一个Future表达式,会立即返回一个 JoinHandle<T>,其中 T 是异步任务(async move { ... } 块)的返回值类型。
JoinHandle是一个句柄,你可以用它来:- 等待任务完成: 使用
task.await。这将返回异步任务的返回值。 - 取消任务: 使用
task.abort()。
- 等待任务完成: 使用
async move { ... }:
- 这是一个异步闭包(async closure)。
- async: 关键字表明这是一个异步函数或闭包,它可以在等待 I/O 或其他异步操作时暂停执行,而不会阻塞整个线程。
- move: 关键字表示这个闭包会获取它所捕获的所有变量的所有权。这意味着闭包内部的代码将拥有这些变量,而不是仅仅借用它们。这对于在 tokio::spawn 中启动的任务非常重要,因为任务可能会在函数作用域之外执行,所以它需要拥有它自己的数据副本或所有权。
Mutex
use tokio::sync::Mutex;:
- tokio::sync::Mutex 是 Tokio 运行时提供的异步互斥锁。
- 与标准的 std::sync::Mutex 不同,tokio::sync::Mutex 是异步的。这意味着当一个任务尝试获取锁但锁已经被另一个任务持有(即发生阻塞)时,它不会阻塞整个线程,而是会让出 CPU 时间,允许其他任务运行,直到锁被释放。这对于在异步环境中(如 Tauri 应用中)管理共享资源非常重要。
- 这里的 Mutex 用来确保在任何时候,只有一个任务可以访问和修改 Option<JoinHandle<()>>。这可以防止在启动新任务和取消旧任务时发生数据不一致的问题。