📚

[Rust]バリア同期を理解する

2023/09/14に公開

はじめに

Rustでは、std::sync::Barrier を使用して、複数のスレッドを特定の同期ポイントで待機させることができます。バリアオブジェクトは、すべてのスレッドがバリアポイントで待機するまで、いずれのスレッドも進めさせないことで同期を提供します。

つまり、ある全てのスレッドで、処理があるところに到達するまで待つということです。JavaScriptでいうPromise.all的な使い方です。

実装

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

fn main() {
    let n_threads = 3; // ここで待つスレッド数を指定する
    let barrier = Arc::new(Barrier::new(n_threads));
    let mut handles = vec![];

    for i in 0..3 {
        let barrier = Arc::clone(&barrier);
        let handle = thread::spawn(move || {
            println!("Thread {} is working.", i);
            // ここで各スレッドはいくつかの作業を行います
            barrier.wait();
            // 全てのスレッドがバリアで待っている間にこの行は実行されません
            println!("Thread {} is past the barrier.", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

この実行結果は以下のようになります。

Thread 1 is working.
Thread 2 is working.
Thread 0 is working.
Thread 0 is past the barrier.
Thread 1 is past the barrier.
Thread 2 is past the barrier.

barrier.wait();まで待つので、各スレッドで、Thread n is working.が実行されてその後wait状態になり、最初に指定したn_threads個のスレッドがwaitに到達するまで待って、その後に一斉に動き出すという感じですね。

ここで、繰り返しを7に、つまりfor i in 0..7とした時の実行結果を示します。

Thread 1 is working.
Thread 4 is working.
Thread 5 is working.
Thread 4 is past the barrier.
Thread 6 is working.
Thread 3 is working.
Thread 2 is working.
Thread 2 is past the barrier.
Thread 1 is past the barrier.
Thread 5 is past the barrier.
Thread 0 is working.
Thread 6 is past the barrier.
Thread 3 is past the barrier.

これはつまり3つ集まった時点で次の処理に行くを繰り返しています。
Thread 1,4,5がwaitに達したので、1,4,5はpast the barrierを表示する処理に進めるのと同時に、他のスレッドは少なくともis workingは表示できるので、これを処理しています。

さらにいうと、このコードでは、waitするスレッド数が3であるのに対して、ループは7回行っていますので、余ったスレッド(今回はThread 0)はずっとwait状態になり、処理は終わりません。

まとめ

どういう時に使うのかはあまり想像できてない(特に、waitするスレッド数より多い数でループを回すみたいな場合)ですが、割と直感的な動作かと思います。

参考文献

Discussion