monoio 设计范式
monoio 是字节跳动开源的一个Rust异步运行时,与其它运行时最大的区别在于设计了一种每个核绑定一个线程,异步任务只在线程内执行,而不会在多线程之间传递。官方解释这种设计的好处在于,异步任务不需要在线程间传递,则不必设计为 Send + Sync,减轻了程序编写的负担。由于异步运行时多在重 I/O、轻计算的情况下发挥优势,这种情况下多线程的优势并不一定大于单线程。
main
在 h2_server example 中,main 函数是这样的
#[monoio::main(threads = 1)]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:5928").unwrap();
println!("listening on {:?}", listener.local_addr());
loop {
if let Ok((socket, _peer_addr)) = listener.accept().await {
monoio::spawn(async move {
if let Err(e) = serve(socket).await {
println!(" -> err={e:?}");
}
});
}
}
}
将过程宏展开之后为
fn main() {
let body = async {
let listener = TcpListener::bind("127.0.0.1:5928").unwrap();
loop {
if let Ok((socket, _peer_addr)) = listener.accept().await {
monoio::spawn(async move {
if let Err(e) = serve(socket).await {
{
::std::io::_print(format_args!(" -> err={0:?}\n", e));
};
}
});
}
}
};
#[allow(clippy::expect_used)]
monoio::RuntimeBuilder::<monoio::FusionDriver>::new()
.build()
.expect("Failed building the Runtime")
.block_on(body)
}
spawn
pub fn spawn<T>(future: T) -> JoinHandle<T::Output>
where
T: Future + 'static,
T::Output: 'static,
{
// 将 future 转换为一个 task 和 一个 join handle
let (task, join) = new_task(
crate::utils::thread_id::get_current_thread_id(),
future,
LocalScheduler,
);
// task 暂存,之后再处理
CURRENT.with(|ctx| {
ctx.tasks.push(task);
});
// join handle 返回给用户
join
}
Submiting a task
monoio 将每个异步 IO 操作表示为一个 Op
pub(crate) struct Op<T: 'static + OpAble> {
// Driver running the operation
pub(super) driver: driver::Inner,
// Operation index in the slab(useless for legacy)
pub(super) index: usize,
// Per-operation data
pub(super) data: Option<T>,
}
使用一个泛型 T 存储具体操作相关的数据,Op<T> 相当于一个 factory,所有的 Op 都有一个 submit_with 方法
impl<T: OpAble> Op<T> {
/// Submit an operation to uring.
pub(super) fn submit_with(data: T) -> io::Result<Op<T>> {
driver::CURRENT.with(|this| this.submit_with(data))
}
}
这个操作同时将一个 Op 操作提交到当前线程,并返回一个具体的 Op 实例。其中 driver::CURRENT 是一个加了一层封装的 thread local key,可以用于将当前的 thread local 变量设置为某个值后执行一个函数,然后再修改回原值。
driver::CURENT 就是一个 thread local 变量,其类型为 Inner,可以表示两种不同的 driver,这里只看 UringDriver。UringDriver 在 submit_with_data 中将 data 封装为 io uring submit queue entry 之后提交到「提交队列」中,然后返回一个 Op, Op 的创建方式为
let mut op = Self::new_op(data, inner, Inner::Uring(this.clone())); // new_op 只是简单将 Op<T> 的各个字段赋值
Op<T> 既然是最终返回的类型,说明它同时也可以被 poll,观察 Op<T> 如何实现 Future trait
impl<T> Future for Op<T>
where
T: Unpin + OpAble + 'static,
{
type Output = Completion<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = &mut *self;
let data_mut = me.data.as_mut().expect("unexpected operation state");
// ready! 只是一个辅助展开 poll_op 结果的宏
// 如果poll_op返回 Pending 则同样返回 Pending
let meta = ready!(me.driver.poll_op::<T>(data_mut, me.index, cx));
me.index = usize::MAX;
let data = me.data.take().expect("unexpected operation state");
Poll::Ready(Completion { data, meta })
}
}
主要看 me.driver.poll_op 发生了什么,io-uring driver 的实现如下
pub(crate) fn poll_op(
this: &Rc<UnsafeCell<UringInner>>,
index: usize,
cx: &mut Context<'_>,
) -> Poll<CompletionMeta> {
let inner = unsafe { &mut *this.get() };
let lifecycle = unsafe { inner.ops.slab.get(index).unwrap_unchecked() };
lifecycle.poll_op(cx)
}
inner.ops.slab 是存储所有 op 的容器,通过 index 获取到对应的 lifecycle,lifecycle 即一个异步任务的生命周期(提交 -> 等待 -> 完成)。
pub(crate) fn poll_op(mut self, cx: &mut Context<'_>) -> Poll<CompletionMeta> {
let ref_mut = &mut self.lifecycle;
match ref_mut {
Lifecycle::Submitted => {
*ref_mut = Lifecycle::Waiting(cx.waker().clone());
return Poll::Pending;
}
Lifecycle::Waiting(waker) => {
if !waker.will_wake(cx.waker()) {
*ref_mut = Lifecycle::Waiting(cx.waker().clone());
}
return Poll::Pending;
}
_ => {}
}
match self.remove().lifecycle {
Lifecycle::Completed(result, flags) => Poll::Ready(CompletionMeta { result, flags }),
_ => unsafe { std::hint::unreachable_unchecked() },
}
}
poll_op 主要检查 op 对应的 lifecycle 是否已经进入了完成状态,是的话将其从容器中删除,并返回结果,否则继续等待。
How a task is completed
在上面的流程中完全没有涉及到一个异步任务是如何被完成的,要探究这一点,需要回到一个测试用例,上面讲到一个过程宏main函数在展开后最终回调用 Runtime::block_on 这个函数,这个函数内部会进入一个双重 loop
loop {
loop {
// Consume all tasks(with max round to prevent io starvation)
let mut max_round = self.context.tasks.len() * 2;
while let Some(t) = self.context.tasks.pop() {
t.run();
if max_round == 0 {
// maybe there's a looping task
break;
} else {
max_round -= 1;
}
}
// Check main future
while should_poll() {
// check if ready
if let std::task::Poll::Ready(t) = join.as_mut().poll(cx) {
return t;
}
}
if self.context.tasks.is_empty() {
// No task to execute, we should wait for io blockingly
// Hot path
break;
}
// Cold path
let _ = self.driver.submit();
}
// Wait and Process CQ(the error is ignored for not debug mode)
#[cfg(not(all(debug_assertions, feature = "debug")))]
let _ = self.driver.park();
#[cfg(all(debug_assertions, feature = "debug"))]
if let Err(e) = self.driver.park() {
trace!("park error: {:?}", e);
}
}
这个循环中包含的信息很多,先看一个 task 是如何运行的。
Harness
task.run() 最终会调用下面的 poll 函数
unsafe fn poll<T: Future, S: Schedule>(ptr: NonNull<Header>) {
let harness = Harness::<T, S>::from_raw(ptr);
harness.poll();
}
这里出现了一个新类型 Harness
// core.rs
#[repr(C)]
pub(crate) struct Cell<T: Future, S> {
pub(crate) header: Header,
pub(crate) core: Core<T, S>,
pub(crate) trailer: Trailer,
}
pub(crate) struct Core<T: Future, S> {
/// Scheduler used to drive this future
pub(crate) scheduler: S,
/// Either the future or the output
pub(crate) stage: CoreStage<T>,
}
pub(crate) struct CoreStage<T: Future> {
stage: UnsafeCell<Stage<T>>,
}
// 表示一个 Future 任务的运行阶段
pub(crate) enum Stage<T: Future> {
Running(T),
Finished(T::Output),
Consumed,
}
#[repr(C)]
pub(crate) struct Header {
/// 表示一个任务的不同状态,如Running, Notified, Idle等
/// 通过usize的不同bit表示一个 State,因此可以表示混合状态
pub(crate) state: State,
/// Table of function pointers for executing actions on the task.
pub(crate) vtable: &'static Vtable,
/// Thread ID(sync: used for wake task on its thread; sync disabled: do checking)
pub(crate) owner_id: usize,
}
pub(crate) struct Trailer {
/// Consumer task waiting on completion of this task.
pub(crate) waker: UnsafeCell<Option<Waker>>,
}
// harness.rs
pub(crate) struct Harness<T: Future, S: 'static> {
cell: NonNull<Cell<T, S>>,
}
来看一下 harness.poll() 发生了什么
impl<T, S> Harness<T, S>
where
T: Future,
S: Schedule,
{
/// Polls the inner future.
pub(super) fn poll(self) {
trace!("MONOIO DEBUG[Harness]:: poll");
match self.poll_inner() {
PollFuture::Notified => {
// We should re-schedule the task.
self.header().state.ref_inc();
// 由于有其它任务 poll,所以需要再次进行 schedule。
self.core().scheduler.yield_now(self.get_new_task());
}
PollFuture::Complete => {
self.complete();
}
PollFuture::Done => (),
}
}
/// 将 header 的 state 转换到 running,在poll一次之后
/// 根据执行状态返回 PollFuture 的不同值
/// PollFuture::Complete => 任务执行完成
/// PollFuture::Done => 任务未执行完成,但是没有其它任务poll它,所以暂时不需要再次 schedule
/// PollFuture::Notified => 任务未执行完成,但是有其它任务poll过,所以需要再次 schedule。
fn poll_inner(&self) -> PollFuture {
// notified -> running
self.header().state.transition_to_running();
let waker_ref = waker_ref::<T, S>(self.header());
let cx = Context::from_waker(&waker_ref);
// check if the stage is Stage::Running(future), if so,
// run future.poll(cx), if the future finish, store output
// and set stage as Stage::Finished(output).
let res = poll_future(&self.core().stage, cx);
if res == Poll::Ready(()) {
return PollFuture::Complete;
}
use super::state::TransitionToIdle;
// 取消 task 的 running 状态
match self.header().state.transition_to_idle() {
TransitionToIdle::Ok => PollFuture::Done,
TransitionToIdle::OkNotified => PollFuture::Notified,
}
}
}
由于 monoio 是与每条线程绑定的,所以只有一种 local scheduler
impl Schedule for LocalScheduler {
// schedule 一个 task 等同于将其加入 task queue 中
fn schedule(&self, task: Task<Self>) {
crate::runtime::CURRENT.with(|cx| cx.tasks.push(task));
}
fn yield_now(&self, task: Task<Self>) {
crate::runtime::CURRENT.with(|cx| cx.tasks.push_front(task));
}
}
再看 RawWakerTable 是如何实现的
pub(super) fn raw_waker<T, S>(header: *const Header) -> RawWaker
where
T: Future,
S: Schedule,
{
let ptr = header as *const ();
let vtable = &RawWakerVTable::new(
clone_waker::<T, S>,
wake_by_val::<T, S>,
wake_by_ref::<T, S>,
drop_waker::<T, S>,
);
RawWaker::new(ptr, vtable)
}
以 wake_by_ref 为例,
unsafe fn wake_by_ref<T, S>(ptr: *const ())
where
T: Future,
S: Schedule,
{
let ptr = NonNull::new_unchecked(ptr as *mut Header);
let harness = Harness::<T, S>::from_raw(ptr);
harness.wake_by_ref();
}
在 harness.wake_by_ref() 中
// haraness.rs
use super::state::TransitionToNotified;
match self.header().state.transition_to_notified() {
// 如果 task 需要提交,调用 scheduler 进行调度。
TransitionToNotified::Submit => {
// # Ref Count: +1 -> task
self.header().state.ref_inc();
self.core().scheduler.schedule(self.get_new_task());
}
TransitionToNotified::DoNothing => (),
}
// state.rs
// 如果一个 task 被 poll,则调用该函数
pub(super) fn transition_to_notified(&self) -> TransitionToNotified {
self.fetch_update_action(|mut curr| {
// 如果当前正在运行,则不需要提交,但是将状态设为 notified
let action = if curr.is_running() {
curr.set_notified();
TransitionToNotified::DoNothing
}
// 如果已经设置,则无事发生
else if curr.is_complete() || curr.is_notified() {
TransitionToNotified::DoNothing
}
// 否则需要设置notified,并进行提交
else {
curr.set_notified();
TransitionToNotified::Submit
};
(action, Some(curr))
})
}
How to wait for I/O events
对于一个 I/O 密集的异步调用链,最终会进入一个等待 I/O 事件的状态,若 task queue 中的所有任务已经执行完成,则在 Runtime::block_on 函数中,会退出里层循环
loop {
loop {
...
if self.context.tasks.is_empty() {
// 任务执行完成,里层循环退出
break;
}
self.driver.park();
}
}
当所有的任务执行完成后,最外层 future 并没有执行完成,则说明还有任务还在等待某个 I/O 事件,此时 driver 需要征询 I/O 事件库(不管是 io-uring 还是 epoll),检查是否有到达的 I/O 事件。
如果有 I/O 事件到达,则每个事件会绑定到一个特定的 token,通过 token 可以在 driver 中获取对应的 waker,从而唤醒对应的 task。以 epoll 驱动的 driver 为例,在 inner_park 中(driver.park 会调用该函数)
let events = unsafe { &mut (*self.inner.get()).events };
// 调用 mio::epoll 库的函数,在 timeout 內到达的事件存入 events 中
match inner.poll.poll(events, timeout) {
Ok(_) => {}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
#[cfg(unix)]
let iter = events.iter();
#[cfg(windows)]
let iter = events.events.iter();
// 迭代所有 event,获取对应的 token
for event in iter {
let token = event.token();
#[cfg(feature = "sync")]
if token != TOKEN_WAKEUP {
inner.dispatch(token, Ready::from_mio(event));
}
#[cfg(not(feature = "sync"))]
inner.dispatch(token, Ready::from_mio(event));
}
inner.dispatch 的实现为
impl LegacyInner {
fn dispatch(&mut self, token: mio::Token, ready: Ready) {
let mut sio = match self.io_dispatch.get(token.0) {
Some(io) => io,
None => {
return;
}
};
let ref_mut = sio.as_mut();
ref_mut.set_readiness(|curr| curr | ready);
ref_mut.wake(ready);
}
}
这里的 sio 是 ScheduledIO 类型,其 wake 实现如下
pub(crate) fn wake(&mut self, ready: Ready) {
if ready.is_readable() {
if let Some(waker) = self.reader.take() {
waker.wake();
}
}
if ready.is_writable() {
if let Some(waker) = self.writer.take() {
waker.wake();
}
}
}
这里的 waker.wake() 就是实际调用虚函数表中的 wake 了,之前已经介绍过这个函数在 schduler 是 LocalScheduler 的情况下,会将 task 加入任务队列中,因此整个逻辑完成闭环。

浙公网安备 33010602011771号