【C++】 barrier(std::barrier)で出来ることと使い方【標準ライブラリ】
目次
barrierとは?
std::barrier
はC++20で導入された同期プリミティブの一つで、複数のスレッドが特定の地点に到達するのを待ち合わせるために使用されます。
std::mutex
や std::condition_variable
を使うよりも簡潔にスレッドの同期を行うことができます。
std::barrier
の主な特徴は以下の通りです:
- 指定した数のスレッドが
arrive_and_wait()
を呼ぶまで待機する - スレッドが到達したら、自動的に再利用(リセット)される
- オプションでフェーズごとに実行される
completion function
を設定可能
barrierの基本的な使い方
まずは std::barrier
の基本的な使い方を見てみましょう。
#include <iostream>
#include <thread>
#include <barrier>
#include <vector>
void task(std::barrier<>& bar, int id) {
std::cout << "スレッド " << id << " は準備中...\n";
bar.arrive_and_wait(); // すべてのスレッドが到達するのを待つ
std::cout << "スレッド " << id << " が開始!\n";
}
int main() {
const int thread_count = 3;
std::barrier bar(thread_count);
std::vector<std::thread> threads;
for (int i = 0; i < thread_count; ++i) {
threads.emplace_back(task, std::ref(bar), i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
ここでは3つのスレッドがすべて arrive_and_wait()
に到達するまで待機し、その後一斉に処理を開始します。
実用的な使用例
std::barrier
は並列処理を段階的に進めるのに便利です。以下に具体的なユースケースを紹介します。
データの並列処理
#include <iostream>
#include <vector>
#include <thread>
#include <barrier>
void process_data(std::vector<int>& data, int start, int end, std::barrier<>& bar) {
for (int i = start; i < end; ++i) {
data[i] *= 2; // 何らかの処理
}
bar.arrive_and_wait(); // すべてのスレッドが処理を終えるまで待機
if (start == 0) {
std::cout << "すべてのデータ処理が完了\n";
}
}
int main() {
std::vector<int> data(10, 1);
std::barrier bar(3);
std::thread t1(process_data, std::ref(data), 0, 3, std::ref(bar));
std::thread t2(process_data, std::ref(data), 3, 7, std::ref(bar));
std::thread t3(process_data, std::ref(data), 7, 10, std::ref(bar));
t1.join();
t2.join();
t3.join();
return 0;
}
他のC++機能との組み合わせ
std::barrier
は std::async
や std::future
などの非同期処理と組み合わせることもできます。
非同期タスクとbarrier
#include <iostream>
#include <future>
#include <barrier>
void async_task(std::barrier<>& bar) {
std::cout << "非同期タスク実行中...\n";
bar.arrive_and_wait();
std::cout << "非同期タスク完了!\n";
}
int main() {
std::barrier bar(2); // メインスレッドと1つの非同期タスク
auto future = std::async(std::launch::async, async_task, std::ref(bar));
bar.arrive_and_wait();
std::cout << "メインスレッドの同期完了\n";
future.wait();
return 0;
}
barrierを使うべき場面
std::barrier
は以下のような場面で有用です:
- 段階的な並列処理(フェーズごとの同期)
- スレッドが一斉に処理を開始する必要がある場合
- 計算タスクの部分的な同期(例えばデータの一部を処理した後に統合する)
barrierの制限と注意点
- スレッドの数を固定する必要がある(
std::latch
とは異なり、動的に増減できない) - フェーズごとに初期化されるため、途中で停止するとデッドロックの可能性がある
まとめ
std::barrier
はC++20で導入された強力な同期ツールです。スレッドを一定のポイントで待機させることで、効率的な並列処理が可能になります。
上記の例を参考に、用途に応じた使い方を試してみてください。