我读了tokio documentation,我想知道将来封装昂贵的同步I / O的最佳方法是什么。
使用reactor框架,我们可以获得绿色线程模型的优势:一些OS线程通过执行程序处理大量并发任务。
未来的tokio模型是需求驱动的,这意味着未来本身将轮询其内部状态以提供有关其完成的信息;允许背压和取消功能。据我了解,未来的投票阶段必须是非阻塞才能运作良好。
我想要封装的I / O可以看作是一个长期的原子和昂贵的操作。理想情况下,一个独立的任务将执行I / O,相关的未来将轮询I / O线程以获得完成状态。
我看到的两个唯一选择是:
poll
功能中包含阻止I / O. 据我了解,这两种解决方案都不是最优的,并且没有充分利用绿色线程模型(首先不在文档中建议,其次不通过reactor框架提供的执行程序)。还有其他解决方案吗?
答案 0 :(得分:6)
理想情况下,一个独立的任务将执行I / O,相关的未来将轮询I / O线程以获得完成状态。
是的,这就是Tokio推荐的内容,以及为futures-cpupool创建的包装箱。请注意,不限于I / O ,但对任何长时间运行的同步任务有效!
在这种情况下,您计划在池中运行闭包。池本身执行工作以检查阻塞闭包是否已完成并满足Future
特征。
use futures::{future, Future}; // 0.1.27
use futures_cpupool::CpuPool; // 0.1.8
use std::thread;
use std::time::Duration;
fn main() {
let pool = CpuPool::new(8);
let a = pool.spawn_fn(|| {
thread::sleep(Duration::from_secs(3));
future::ok::<_, ()>(3)
});
let b = pool.spawn_fn(|| {
thread::sleep(Duration::from_secs(1));
future::ok::<_, ()>(1)
});
let c = a.join(b).map(|(a, b)| a + b);
let result = c.wait();
println!("{:?}", result);
}
请注意,这是不一种有效的睡眠方式,它只是一个阻塞操作的占位符。如果您确实需要睡觉,请使用futures-timer或tokio-timer之类的内容。有关详细信息,请参阅Why does Future::select choose the future with a longer sleep period first?
您可以看到总时间仅为3秒:
$ time ./target/debug/example
Ok(4)
real 0m3.021s
user 0m0.008s
sys 0m0.009s
似乎tokio-threadpool可以用于相同的结果:
use std::{thread, time::Duration};
use tokio::{prelude::*, runtime::Runtime}; // 0.1.20
use tokio_threadpool; // 0.1.14
fn delay_for(seconds: u64) -> impl Future<Item = u64, Error = tokio_threadpool::BlockingError> {
future::poll_fn(move || {
tokio_threadpool::blocking(|| {
thread::sleep(Duration::from_secs(seconds));
seconds
})
})
}
fn main() {
let a = delay_for(3);
let b = delay_for(1);
let sum = a.join(b).map(|(a, b)| a + b);
let mut runtime = Runtime::new().expect("Unable to start the runtime");
let result = runtime.block_on(sum);
println!("{:?}", result);
}
但是,运行此代码表明需要4秒钟:
$ time ./target/debug/example
Ok(4)
real 0m4.033s
user 0m0.015s
sys 0m0.012s
the documentation for blocking
(强调我的)触及了这一点:
只要提供的闭包阻止,即使您使用了
blocking
之类的未来组合器,也会阻止调用select
的整个任务 - 此任务中的其他未来将会在关闭返回之前没有进展。如果不需要,请确保blocking
在其自己的任务中运行(例如,使用futures::sync::oneshot::spawn
)。
这看起来像这样:
use futures; // 0.1.27
use std::{thread, time::Duration};
use tokio::{executor::DefaultExecutor, prelude::*, runtime::Runtime}; // 0.1.20
use tokio_threadpool; // 0.1.14
fn delay_for(seconds: u64) -> impl Future<Item = u64, Error = tokio_threadpool::BlockingError> {
futures::lazy(move || {
let f = future::poll_fn(move || {
tokio_threadpool::blocking(|| {
thread::sleep(Duration::from_secs(seconds));
seconds
})
});
futures::sync::oneshot::spawn(f, &DefaultExecutor::current())
})
}
fn main() {
let a = delay_for(3);
let b = delay_for(1);
let sum = a.join(b).map(|(a, b)| a + b);
let mut runtime = Runtime::new().expect("Unable to start the runtime");
let result = runtime.block_on(sum);
println!("{:?}", result);
}
这两种解决方案都不是最佳解决方案,并且没有充分利用绿色线程模型
这是对的 - 因为你没有异步的东西!您正在尝试将两种不同的方法结合起来,并且必须在某处某处进行翻译。
第二个不通过reactor框架提供的执行程序
我不确定你的意思。在上面的例子中只有一个执行者; wait
隐式创建的那个。线程池有一些内部逻辑,用于检查线程是否完成,但只应在用户的执行者poll
时触发。