自分用の備忘録

VimとかJSとか。やったことのメモ。自分のため。

Rustで並列処理

Rustで並列処理をやりましょう。

恐れるな!並行性 基本的にはここを読めばできます。

thread::spawnを使ったりなんやかんやするのですが、今回はtokio::spawnの方を使います。

使い方

use tokio::time::{sleep, Duration}
#[tokio::main]
fn main() {
    let handle1 = tokio::spawn(async {
        sleep(Duration::from_secs(5)).await;
        println!("Task1 !!!");
    });
    let handle2 = tokio::spawn(async {
        sleep(Duration::from_secs(2)).await;
        println!("Task2 !!!");
    });
    // それぞれの終了を待つ
    handle1.await.unwrap();
    handle2.await.unwrap();
}

これだけ。
普通にthread::spawnと書き方もあんまり変わらないですね。

それぞれ、クロージャ外で定義した変数を使いたい場合は、moveで、クロージャに所有権を移します。

use tokio::time::{sleep, Duration}
#[tokio::main]
fn main() {
    let message1 = "Task1!";
    let message2 = "Task2!";

    let handle1 = tokio::spawn(async move {
        sleep(Duration::from_secs(2)).await;
        println!("Task1-1: {}", Local::now());
        sleep(Duration::from_secs(3)).await;
        println!("{}", message1);
    });
    let handle2 = tokio::spawn(async move {
        sleep(Duration::from_secs(2)).await;
        println!("{}: {}", message2, Local::now());
    });
    // それぞれの終了を待つ
    handle1.await.unwrap();
    handle2.await.unwrap();
    println!("Hello!");
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=57b249c23003de6812b2d38c956dea30

Task1-1, Task2の表示がほぼ同時にくることが分かると思います。
同時に動いていることがわかります。

実用的なやつ

さて、おそらくこういったことが実際使いたい場面は、ループが絡んでそうです。

async fn async_fn(arg: i32) -> Result<()> {
    // なんかかする
    Ok(()
}

#[tokio::main]
async fn main() {
    let items = // 何かしら読み込む itemsはVec<T>
    let tasks = new Vec(); // Vec<JobHandle>
    for item in items {
        let task = tokio::task::spawn(async move {
            async_fn(&item.id).await;
        });
        tasks.push(task);
    }
    for task in tasks {
        match task.await {
            Ok(_) => println!("Success!"),
            Err(e) => println!("{}", e)
        }
    }
}

こんなイメージで使えるかと。
DBから並列でデータとってきてログうんぬんのやつなら、

let db_connections: Vec<Connection> = // ...
let tasks = new Vec(); // Vec<Jobhandle>
for connection in db_connections {
    let task = tokio::task::spawn(async move {
        fetch_log(&connection).await;
    });
    tasks.push(tesk);
}
// ...

的なイメージになると思います。
ただ、この場合は、並列でとったデータをうまい具合に結合してあげて...。
みたいなのもいりそう。

https://doc.rust-lang.org/std/future/trait.Future.html

こういうの使って、join_allでまとめて、どうこうとか。
そういう感じかもしれないです。(試してない)

一応Rustで並列処理が書けるようになりました。