Open4

Rustのtokio::mainのthreadどうなってるの?

reoringreoring

Tokio mainってどうなってるのか。まずドキュメントを読む

https://docs.rs/tokio/latest/tokio/attr.main.html

#[tokio::main(flavor = "multi_thread", worker_threads = 10)]

The worker_threads option configures the number of worker threads, and defaults to the number of cpus on the system. This is the default flavor.

Note: The multi-threaded runtime requires the rt-multi-thread feature flag.

なるほど。multi_threadがデフォルトなのか。

reoringreoring

tokio::mainが実際どういうコードを出力しているか確認していく。

cargo new tokio-main-test
cargo add tokio --features="macros, rt, rt-multi-thread"

で、cargo-expandもいれとく

cargo add cargo-expand

cargo-expandはnightlyじゃないと動作しないのでrustupで上書きしとく

rustup override set nightly
reoringreoring

で、mainを書いてみる。

#[tokio::main]
async fn main() {
    println!("Hello, world!");
}

このままだと待たないので、3秒待つ関数を追加する。

#[tokio::main]
async fn main() {
    println!("start");
    wait().await;
    println!("finish");
}

async fn wait() {
    tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}

この状態でcargo runとかで実行すると、startから3秒まってfinishが出力される。

これをcargo expandで展開すると下記のコードになる。

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
    let body = async {
        {
            ::std::io::_print(format_args!("start\n"));
        };
        wait().await;
        {
            ::std::io::_print(format_args!("finish\n"));
        };
    };
    #[allow(clippy::expect_used, clippy::diverging_sub_expression)]
    {
        return tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .expect("Failed building the Runtime")
            .block_on(body);
    }
}
async fn wait() {
    tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}

bodyは単純にmainの中身を包んだものになる。
ドキュメントにある通り、tokioのランタイムはデフォルトでmulti_threadが選択されているようだ。

reoringreoring

次に、tokio runtimeのmulti-threadがどうなっているか見ていく。

https://docs.rs/tokio/latest/src/tokio/runtime/builder.rs.html#217-220

        pub fn new_multi_thread() -> Builder {
            // The number `61` is fairly arbitrary. I believe this value was copied from golang.
            Builder::new(Kind::MultiThread, 61, 61)
        }

new_multi_thread()はBuilderを返している。

https://docs.rs/tokio/latest/src/tokio/runtime/builder.rs.html#638

    pub fn build(&mut self) -> io::Result<Runtime> {
        match &self.kind {
            Kind::CurrentThread => self.build_current_thread_runtime(),
            #[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
            Kind::MultiThread => self.build_threaded_runtime(),
        }
    }

builderのbuild()を実行すると、この場合だとbuild_threaded_runtimeが実行される。

最終的に、

Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool)

で、MultiThreadのSchedulerが返ることになる。

このSchedulerがマルチスレッドのスケジューリングを提供しているようだ。

https://docs.rs/tokio/latest/src/tokio/runtime/scheduler/multi_thread/mod.rs.html

この中でPark/Unparkをしている。