展开描述
Tokio 运行时。
与其他 Rust 程序不同,异步应用需要运行时 support. In particular, the following runtime services are necessary:
- 一个 I/O 事件循环(称为 driver),它驱动 I/O 资源,并把 I/O 事件分发给依赖这些资源的任务。
- 一个 调度器,用于执行使用这些 I/O 资源的任务。
- 一个 定时器,用于在一段时间后调度工作的运行。
Tokio 的Runtime将这些服务捆绑为单一类型,允许它们一起启动、关闭和配置。但通常不需要手动配置Runtime,用户可以直接使用tokio::main属性宏,它会在底层创建一个Runtime。
§Choose your runtime
以下是为应用选择合适运行时的经验法则。
+------------------------------------------------------+
| Do you want work-stealing or multi-thread scheduler? |
+------------------------------------------------------+
| Yes | No
| |
| |
v |
+------------------------+ |
| Multi-threaded Runtime | |
+------------------------+ |
|
V
+--------------------------------+
| Do you execute `!Send` Future? |
+--------------------------------+
| Yes | No
| |
V |
+---------------+ |
| Local Runtime | |
+---------------+ |
|
v
+------------------------+
| Current-thread Runtime |
+------------------------+上面的决策树并不详尽。还有其他可能影响你决定的因素。
§与同步代码桥接
有关详细信息,请参阅 https://tokio.rs/tokio/topics/bridging。
§NUMA 感知
tokio 运行时不是 NUMA(非一致性内存访问)感知的。 You may want to start multiple runtimes instead of a single runtime for better performance on NUMA systems.
§Usage
当不需要细粒度调整时,
可以使用 tokio::main 属性宏。
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?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) => n,
Err(e) => {
println!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
println!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
}在运行时上下文中,
额外的任务
使用 tokio::spawn
函数派生。
使用此函数派生的 future
将在 Runtime
使用的同一线程池上执行。
也可以直接使用一个 Runtime 实例。
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::runtime::Runtime;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create the runtime
let rt = Runtime::new()?;
// Spawn the root task
rt.block_on(async {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) => n,
Err(e) => {
println!("failed to read from socket; err = {:?}", e);
return;
}
};
// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
println!("failed to write to socket; err = {:?}", e);
return;
}
}
});
}
})
}§运行时配置
Tokio 提供了多种任务调度策略,
适用于不同的应用程序。
运行时构建器 或
#[tokio::main] 属性
可用于选择要使用的调度器。
§Multi-Thread Scheduler
多线程调度器在线程池上
使用工作窃取策略执行 future。
默认情况下,
它将为系统上每个可用的 CPU 核心
启动一个工作线程。
这通常是大多数应用程序的理想配置。
多线程调度器需要
rt-multi-thread
feature flag,
并且默认选中:
use tokio::runtime;
let threaded_rt = runtime::Runtime::new()?;大多数应用程序应使用多线程调度器, 除非在某些小众用例中,例如仅需要运行单个线程时。
§Current-Thread Scheduler
当前线程调度器提供了一个单线程的
future 执行器。
所有任务都将在当前线程上
创建和执行。
这需要 rt feature flag。
use tokio::runtime;
let rt = runtime::Builder::new_current_thread()
.build()?;§Resource drivers
手动配置运行时时,
默认不会启用
任何资源 driver。
在这种情况下,
尝试使用网络类型或时间类型
将失败。
为了启用这些类型,
必须启用资源 driver。
这是通过
Builder::enable_io
和
Builder::enable_time
来完成的。
作为简写,
Builder::enable_all
启用两个资源 driver。
§驱动运行时
Tokio 运行时仅在运行时正在运行时
才能执行任务。
通常这不是问题,
因为运行时的默认配置始终在运行,
但诸如 current-thread 之类的替代配置
要求
调用 Runtime::block_on。
- A multi-threaded runtime is always running because it spawns its own worker threads.
- A current-thread runtime does not spawn any worker threads, so it can only
execute tasks when you provide a thread by calling
Runtime::block_on. - A
LocalSetonly executes local tasks spawned on it when theLocalSetis.awaitedor otherwise driven using one of its methods for this purpose.
请注意,
Handle::block_on
不会驱动运行时。
在使用 current
thread 运行时时,
必须至少调用一次
Runtime::block_on。
仅 Handle::block_on
是不够的。
§派生线程的生命周期
运行时可能会根据其配置和使用情况
派生线程。
多线程调度器派生线程
以调度任务
和用于 spawn_blocking 调用。
当 Runtime 处于活动状态时,
线程可能会在空闲一段时间后
关闭。
一旦 Runtime 被丢弃,
所有运行时线程通常已被终止,
但在存在无法停止的已派生工作的情况下,
不保证已被终止。
有关更多详细信息,
请参阅
struct 级别的文档。
§Detailed runtime behavior
本节详细介绍了 Tokio 运行时将如何调度 tasks for execution.
在最基础的层面,
运行时有一个
需要调度的任务集合。
它将反复地从该集合中移除一个任务
并调度它(通过调用
poll)。
当集合为空时,
线程将进入休眠,
直到有任务被添加到集合中。
然而,上述并不足以保证运行时表现良好。 For example, the runtime might have a single task that is always ready to be scheduled, and schedule that task every time. This is a problem because it starves other tasks by not scheduling them. To solve this, Tokio provides the following fairness guarantee:
如果任务总数不会无限制增长,且没有任务 blocking the thread, then it is guaranteed that tasks are scheduled fairly.
或者,更正式地说:
在以下两个假设下:
- There is some number
MAX_TASKSsuch that the total number of tasks on the runtime at any specific point in time never exceedsMAX_TASKS.- There is some number
MAX_SCHEDULEsuch that callingpollon any task spawned on the runtime returns withinMAX_SCHEDULEtime units.那么, 存在某个数
MAX_DELAY, 使得当一个任务被唤醒时, 它将在MAX_DELAY个时间单位内被运行时调度。
(这里,MAX_TASKS 和
MAX_SCHEDULE 可以是任意数字,
运行时的用户可以选择它们。
MAX_DELAY 数字由
运行时控制,
取决于 MAX_TASKS 和
MAX_SCHEDULE 的值。)
除了上述的公平性保证之外,
任务调度的顺序没有任何保证。
也不能保证运行时
对所有任务
都同样公平。
例如,
如果运行时有两个任务 A 和 B
都已就绪,
那么运行时可能会在调度 B 之前
调度 A 五次。
即使 A 使用
yield_now
让出,
也是如此。
唯一保证的是
它最终会调度 B。
通常,
任务仅在通过在其 waker 上
调用 wake
被唤醒时才会被调度。
但这无法保证,
Tokio 在某些情况下
可能会调度尚未被唤醒的任务。
这称为虚假唤醒。
§IO 和定时器
除了调度任务之外,运行时还必须管理 IO 资源和 timers. It does this by periodically checking whether there are any IO resources or timers that are ready, and waking the relevant task so that it will be scheduled.
这些检查在调度任务之间定期执行。在以下条件下 same assumptions as the previous fairness guarantee, Tokio guarantees that it will wake tasks with an IO or timer event within some maximum number of time units.
§当前线程运行时(撰写时的行为)
本节描述 current thread runtime 当前的行为。 该行为可能会在 Tokio 的未来版本中发生变化。
current thread runtime 维护两个 FIFO 任务队列,
分别用于准备调度的任务:
全局队列和本地队列。
运行时将优先从本地队列中
选择下一个要调度的任务,
仅当本地队列为空时,
或当其已从本地队列
连续挑出 31 次任务时,
才会从全局队列中选取一个任务。
数字 31 可以使用
global_queue_interval
设置进行更改。
当没有任务准备好被调度时,
或者当运行时已连续调度 61 个任务时,
它会检查新的 IO 或 timer 事件。
数字 61 可以通过
event_interval
设置进行更改。
当从运行时上运行的任务内部唤醒一个任务时,则 woken task is added directly to the local queue. Otherwise, the task is added to the global queue. The current thread runtime does not use the lifo slot optimization.
§多线程运行时(撰写时的行为)
本节描述 multi thread runtime 当前的行为。 该行为可能会在 Tokio 的未来版本中发生变化。
多线程运行时具有固定数量的工作线程,所有这些工作线程 created on startup. The multi thread runtime maintains one global queue, and a local queue for each worker thread. The local queue of a worker thread can fit at most 256 tasks. If more than 256 tasks are added to the local queue, then half of them are moved to the global queue to make space.
运行时将优先从本地队列中
选择下一个要调度的任务,
仅当本地队列为空时,
或当其已从本地队列连续挑出
global_queue_interval
次任务时,
才会从全局队列中选取一个任务。
如果未显式设置
global_queue_interval
的值,
则运行时会动态计算它,
使用一个启发式,
目标是在每次检查全局队列之间
保持 10ms 的间隔(基于
worker_mean_poll_time
指标)。
如果本地队列和全局队列都为空,则工作线程 will attempt to steal tasks from the local queue of another worker thread. Stealing is done by moving half of the tasks in one local queue to another local queue.
当没有任务准备好被调度时,
或者当运行时已连续调度 61 个任务时,
它会检查新的 IO 或 timer 事件。
数字 61 可以通过
event_interval
设置进行更改。
multi thread runtime 使用 lifo slot optimization:
每当一个任务唤醒另一个任务时,
另一个任务会被添加到工作线程的 lifo slot,
而不是被添加到队列中。
如果 lifo slot 中已经有一个任务,
则该 lifo slot 将被替换,
原本在该 lifo slot 中的任务
将被放入线程的本地队列中。
当运行时完成调度一个任务时,
它将立即调度 lifo slot 中的任务(如果有)。
当 lifo slot 被使用时,
coop
budget
不会被重置。
此外,
如果一个工作线程
连续使用 lifo slot 三次,
则会被临时禁用,
直到该工作线程调度了一个
不是来自 lifo slot 的任务。
lifo slot 可以使用
disable_lifo_slot
设置被禁用。
lifo slot 与本地队列是分开的,
因此其他工作线程
无法窃取 lifo slot 中的任务。
当从不是工作线程的线程唤醒任务时,则 task is placed in the global queue.
§Performance tuning
§文件描述符表预热
在 Linux 上,
文件描述符表的增长可能会阻塞工作线程。
请参阅
prewarm-fd-table
示例。
结构体§
- Builder
- 使用自定义配置值构建 Tokio Runtime。
- Enter
Guard - 运行时上下文守卫。
- Handle
- 运行时的句柄。
- Id
- 一个不透明的 ID,相对于所有其他当前 running runtimes.
- Local
Options LocalRuntime专用配置选项- Local
Runtime - 一个本地 Tokio 运行时。
- Runtime
- Tokio 运行时。
- Runtime
Metrics - 运行时 metrics 的句柄。
- TryCurrent
Error - 当没有 Runtime 启动时,由
try_current返回的错误
枚举§
- Runtime
Flavor Runtime的运行模式(flavor)。
函数§
- is_
rt_ shutdown_ err - 检查给定的错误是否由 Tokio 在关闭其运行时时发出。