-
简介
tokio是rust的io事件驱动库,本文主要介绍了tokio的runtime,内容来自官方文档,对应tokio版本v1.38.0 -
异步应用程序需要的运行时支持
I/O事件循环。
执行使用I/O资源的任务调度程序。
定时器 -
.运行时的使用
使用宏的方式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(n) if n == 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; } } }); }
}
```
使用手动设置
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(n) if n == 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;
}
}
});
}
})
}
-
运行时的配置
5.1 调度器选择
多线程调度
需要rt-multi-thread特性支持,使用了线程池,线程之间使用的任务调度策略使用的是work-stealing(当前线程任务队列是空的时,可以从其他线程取任务,以便把任务在各个线程上均匀分布),默认情况下在每个cpu核心上启动一个工作线程,大部分应用使用此调度策略。当前线程调度
单线程调度,所有任务在一个线程上执行。
5.2 资源驱动
当使用手动设置运行时的情况下,默认i/o和定时器无法使用,需要设置Builder::enable_io and Builder::enable_time分别开启相应资源,可以使用Builder::enable_all同时开启两个。
5.3 线程的生命周期
当运行时正常时,线程执行结束会可以结束。
当运行时异常结束时,线程都会停止。
5.4 详细运行时行为
当任务调度时,tokio为防止个别任务频率过高而调度不到其他任务做了个策略,使用三个变量
MAX_TASKS,MAX_SCHEDULE,MAX_DELAY。
5.5 I/O和定时器
除了调度任务,还需要检查i/o和定时器事件是否准备就绪,如果就绪就唤醒相应任务准备调度
单线程
维护了两个队列,一个全局的,一个本地的,调度的时候优先从本地队列中获取任务,当本地任务是空的,或者从本地连续获取了31次任务进行调度,那么就会从全局队列获取,31可配置
当没有任务被调度,或者连续调度了61个任务,那么将检查i/o和定时器是否有任务就绪,61可配置。如果正在执行的任务唤醒了另一个任务,此任务直接加入本地队列,否则加入全局队列。
多线程
拥有固定个数的工作线程,和一个全局的队列,每个工作线程拥有一个本地队列,本地队列最多有256个任务,如果超过直接把一半任务移动到全局队列。
优先选择从本地队列获取任务进行调度,如果本地队列是空的,或者工作线程连续从本地队列调度了global_queue_interval次任务,那么会从全局队列获取任务进行调度,如果global_queue_interval没有被显示的设置,那么由tokio进行动态推断global_queue_interval的值。
如果全局队列和本地队列都是空的,就会从其他工作线程的本地队列获取,每次获取对方的一半数量的任务,
工作线程没有任务调度,或者连续调度任务61(可设置)次会检查i/o和定时器是否有任务就绪。
多线程使用了lifo槽优化,如果一个任务唤醒了另一个任务,此时唤醒的任务加入lifo槽中,当槽中已经有任务了,会直接把槽中的任务放到本地队列,此任务然后再放入槽中。当运行中的任务结束之后会立即执行Lifo槽中的任务,如果调度的任务连续来自槽中,tokio会临时禁止调度槽中任务,直到有一个调度的任务不是来自槽中,由于槽中数据和本地线程是分离的,其他线程无法获取本线程槽中的任务。如果一个任务不是工作线程获取的,它会被放入到全局队列。