B-Tree実装でわかる

std::sync::Mutexとtokio::sync::Mutexの違い

Yoshiaki

スライドのQRコード

スライドのQRコード https://yoshisaur.net/presentations/UV_Study/index.html

前置き

このスライドは理論的な厳密さよりはわかりやすさと勢いを意識しています

今回実装しているB-Tree自体まだ完璧ではない

同じことをやっておられた方が先にいた

「RustでOn-diskなB+Treeを作ったときの細かな話」 By KOBA789

std::sync::Mutexとtokio::sync::Mutexの違い

stdの提供しているapiはブロッキング

tokioの提供しているapiはノンブロッキング

ブロッキング VS ノンブロッキング

ブロッキングでやらかす例


    #[tokio::main(worker_threads = 1)]
    async fn main() -> Result<(), Box> {
        // ノンブロッキングタスク
        let non_blocking_task = tokio::task::spawn(async {
            println!("Hello from non-blocking task");
            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
            println!("Bye from non-blocking task");
        });
        // ブロッキングタスク
        let blocking_task = tokio::task::spawn(async {
            println!("Hello from blocking task");
            std::thread::sleep(std::time::Duration::from_secs(5));
            println!("Bye from blocking task");
        });
        // どちらも終わるのを待つ
        non_blocking_task.await?;
        blocking_task.await?;
        Ok(())
    }
          

出力:


    Hello from non-blocking task
    Hello from blocking task
    Bye from blocking task
    Bye from non-blocking task
                        
    速く終わるはずのタスクが遅く終わる
          

どうしてもブロッキングな処理をしたい場合はspawn_blockingを使おう

基本的にはtokioのノンブロッキングなAPIを使えば問題はないけど、

CPUの消費が激しいタスクやブロッキングタスクを実行するときはこれでいい


    let blocking_task = tokio::task::spawn_blocking(|| {
        println!("Hello from blocking task");
        std::thread::sleep(std::time::Duration::from_secs(5));
        println!("Bye from blocking task");
          

めでたし、ではない

tokioの入口としてはノンブロッキング大事だけど

全て忘れていいよ!

俺のイケてるB-Tree見てくれよ

シン・B-Tree実装でわかる

std::sync::Mutexとtokio::sync::Mutexの違い

Yoshiaki

これさえ覚えてればいい(Mutexのノリ)


use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Arcで包んだMutexでカウンタを作成
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        // 各スレッドにカウンタのクローンを渡す
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    // 全てのスレッドの終了を待つ
    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}
          

これさえ覚えてればいい(MutexGuard1)


// std::sync
pub struct MutexGuard<'a, T: ?Sized + 'a>
          

std::sync::Mutex<T>なやつからlock().unwrap()で取れるやつ

MutexGuard<'a, T>: ライフタイムパラメータ 'a を持つ。

Mutexがスコープから外れるとMutexGuardも解放される

これさえ覚えてればいい(MutexGuard2)


// tokio::sync
pub struct OwnedMutexGuard<T: ?Sized>
          

tokio::async::Mutex<T>なやつからlock().awaitで取れるやつ

OwnedMutexGuard<T>: ライフタイムパラメータがない。

所有権に基づいて動作し、非同期コンテキストで使用される

B-Treeのコード

ie-Yoshisaur/concurrent-b-tree

並行でB-Treeにアクセスできる

正直にいうとベンチマーク測ったらシングルスレッドの方が速い

B-Tree可視化くん

B-Tree可視化くん

説明に使うやつ

ありがとうございました