我实现了一个玩具 UDP 服务器,它通过多个并发任务尽可能快地通过一组套接字喷出数据。我天真地认为这可以通过利用 Tokio 的线程运行时来有效利用 CPU 资源:
use std::error::Error;
use std::io;
use tokio::net::UdpSocket;
struct Server {
socket: UdpSocket,
buf: Vec<u8>,
id: u32,
}
const T_LOOPS: usize = 100000;
impl Server {
async fn run(self) -> Result<(), io::Error> {
let Server {
mut socket,
buf,
id,
} = self;
let peer = "127.0.0.1:9876".to_string();
loop {
for _n in 0..T_LOOPS {
let _amt = socket.send_to(&buf[..], &peer).await?;
}
println!("server {} run {} loops", id, T_LOOPS);
}
}
}
#[tokio::main(max_threads=8)]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = "0.0.0.0:0".to_string();
for n in 0u32..4u32 {
let socket = UdpSocket::bind(&addr).await?;
let server = Server {
socket: socket,
buf: vec![0; 1500],
id: n,
};
tokio::spawn(async move {
server.run().await
});
}
Ok(())
}
它实践,哪些任务运行似乎是相当不确定的。通常第一个生成的任务会运行,其他 3 个任务中的任意数量都会运行(并不总是按顺序运行)。例如,输出可能类似于:
server 0 run 100000 loops
server 0 run 100000 loops
server 0 run 100000 loops
或者
server 1 run 100000 loops
server 0 run 100000 loops
server 2 run 100000 loops
server 1 run 100000 loops
server 2 run 100000 loops
server 0 run 100000 loops
但并不是我所需要的(这将显示所有 4 个 ID)。
我不禁觉得我一定在这里遗漏了一些东西。我玩过core-threads
但max-threads
没有任何有用的结果。
如何使这种类型的系统与 Tokio(或一般异步)可靠地工作?
虽然这是一个玩具系统,但它实际上是我想要的用例的垫脚石。