C++のstd::shared_futureを使った複数スレッドでの結果共有方法

C++における並行処理の重要性が年々高まっています。特に、複数のスレッドで非同期に処理を行い、その結果を効率的に共有する方法が求められています。ここで登場するのが、C++標準ライブラリの一部であるstd::shared_futureです。std::shared_futureを使用することで、複数のスレッド間で結果を安全かつ簡単に共有することができます。本記事では、std::shared_futureの基本的な使い方から応用例までを詳しく解説し、並行処理の効率を最大限に引き出す方法を紹介します。

目次

std::futureとstd::shared_futureの違い

C++には、非同期タスクの結果を扱うためのメカニズムとしてstd::futurestd::shared_futureがあります。これらは共に非同期タスクの結果を取得するための手段ですが、その使い方と特性には重要な違いがあります。

std::futureの概要

std::futureは、非同期タスクの結果を一度だけ取得するために使用されます。一度結果を取得すると、それ以上その結果にアクセスすることはできません。これはシングルオーナーのリソース管理に似ています。

#include <future>
#include <iostream>

int async_task() {
    return 42;
}

int main() {
    std::future<int> future = std::async(std::launch::async, async_task);
    int result = future.get();
    std::cout << "Result: " << result << std::endl;
    // future.get(); // 再度呼び出すと例外が発生
    return 0;
}

std::shared_futureの概要

一方、std::shared_futureは、複数のスレッドから同じ結果にアクセスできるように設計されています。std::shared_futureはコピー可能であり、コピーされたオブジェクトからも結果を取得できます。これにより、同じ結果を複数回取得することが可能となり、複数スレッド間での結果共有が容易になります。

#include <future>
#include <iostream>

int async_task() {
    return 42;
}

int main() {
    std::future<int> future = std::async(std::launch::async, async_task);
    std::shared_future<int> sharedFuture = future.share();
    int result1 = sharedFuture.get();
    int result2 = sharedFuture.get();
    std::cout << "Result1: " << result1 << ", Result2: " << result2 << std::endl;
    return 0;
}

違いのまとめ

  • 使用目的:
  • std::future: 結果を一度だけ取得する場合に使用。
  • std::shared_future: 結果を複数回、または複数スレッドから取得する場合に使用。
  • コピー可能性:
  • std::future: コピー不可。
  • std::shared_future: コピー可能。

これらの違いを理解することで、適切な場面でstd::futurestd::shared_futureを使い分けることができます。次章では、std::shared_futureの基本的な使い方についてさらに詳しく見ていきます。

std::shared_futureの基本的な使い方

std::shared_futureは、複数のスレッドから同じ結果にアクセスできるように設計されており、並行処理を行う際に非常に便利です。ここでは、std::shared_futureの基本的な使い方とその利点について解説します。

std::shared_futureの生成方法

std::shared_futureは、std::futureオブジェクトから生成されます。std::futureオブジェクトをshare()メソッドで変換することで、std::shared_futureオブジェクトを取得できます。

#include <future>
#include <iostream>

int async_task() {
    return 42;
}

int main() {
    std::future<int> future = std::async(std::launch::async, async_task);
    std::shared_future<int> sharedFuture = future.share();
    std::cout << "Shared Future created" << std::endl;
    return 0;
}

結果の取得方法

std::shared_futureから結果を取得する方法は、std::futureと同じくget()メソッドを使用します。ただし、std::shared_futureはコピー可能であり、コピーされたオブジェクトからも同じ結果を取得できます。

#include <future>
#include <iostream>

int async_task() {
    return 42;
}

int main() {
    std::future<int> future = std::async(std::launch::async, async_task);
    std::shared_future<int> sharedFuture = future.share();

    std::shared_future<int> copy1 = sharedFuture;
    std::shared_future<int> copy2 = sharedFuture;

    int result1 = copy1.get();
    int result2 = copy2.get();

    std::cout << "Result1: " << result1 << ", Result2: " << result2 << std::endl;
    return 0;
}

利点と応用

std::shared_futureを使用することで、以下のような利点があります:

  1. 複数スレッドからのアクセス:
  • 複数のスレッドが同じ結果にアクセスする場合、std::shared_futureを使用することで結果の再計算を防ぎます。
  1. 結果のキャッシング:
  • 同じ計算結果を何度も使用する場合に便利です。一度計算した結果を複数回使用できます。

これにより、並行処理の効率が向上し、コードの可読性も高まります。次章では、実際に複数スレッドでstd::shared_futureを活用する方法について具体例を示します。

複数スレッドでのstd::shared_futureの活用

複数スレッドで結果を共有する場合、std::shared_futureを使用すると非常に便利です。ここでは、具体的な例を通じて、複数スレッドでstd::shared_futureをどのように活用するかを解説します。

基本的な使用例

以下の例では、非同期タスクを実行し、その結果を複数のスレッドで共有します。std::asyncを使って非同期タスクを起動し、その結果をstd::shared_futureで共有しています。

#include <future>
#include <iostream>
#include <thread>
#include <vector>

int async_task() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate a time-consuming task
    return 42;
}

void worker(std::shared_future<int> sharedFuture, int workerId) {
    int result = sharedFuture.get();
    std::cout << "Worker " << workerId << " received result: " << result << std::endl;
}

int main() {
    // Launch an asynchronous task
    std::future<int> future = std::async(std::launch::async, async_task);
    // Convert to shared_future
    std::shared_future<int> sharedFuture = future.share();

    // Launch multiple threads that share the same future
    std::vector<std::thread> workers;
    for (int i = 0; i < 5; ++i) {
        workers.emplace_back(worker, sharedFuture, i);
    }

    // Join all threads
    for (auto& worker : workers) {
        worker.join();
    }

    return 0;
}

この例では、非同期タスクasync_taskが結果を計算し、その結果をsharedFutureに格納します。複数のスレッドがworker関数を実行し、sharedFutureから結果を取得しています。このように、std::shared_futureを使うことで、同じ結果を複数のスレッドで共有することができます。

並行処理における利点

std::shared_futureを使用することで、以下のような利点があります:

  1. 効率的なリソース利用:
  • 結果を一度だけ計算し、複数のスレッドで共有できるため、計算コストを削減できます。
  1. コードのシンプル化:
  • 同じ結果を共有するための複雑なロジックを省略でき、コードがシンプルになります。
  1. 安全なアクセス:
  • std::shared_futureはスレッドセーフであり、複数のスレッドが同時に結果にアクセスしても安全です。

注意点

std::shared_futureを使用する際には、以下の点に注意が必要です:

  • 結果の遅延取得:
  • get()メソッドを呼び出すまで結果の取得が遅延するため、非同期タスクが完了するまでブロックされることがあります。
  • スレッドの同期:
  • 複数のスレッドが結果を取得するタイミングによっては、意図しないタイミングでのブロックが発生する可能性があります。

次章では、さらに具体的なコード例を用いてstd::shared_futureを使った並行処理の実装方法について詳しく見ていきます。

std::shared_futureを使った並行処理の具体例

ここでは、std::shared_futureを使った並行処理の具体的なコード例を紹介します。この例では、複数のスレッドが非同期タスクの結果を共有し、その結果を元に別の計算を行います。

具体的なシナリオ

以下のシナリオを考えます:

  1. 非同期タスクで重い計算を行い、その結果を取得する。
  2. 複数のスレッドがその結果を使用して追加の計算を行う。

コード例

次のコードでは、std::asyncで非同期タスクを実行し、その結果をstd::shared_futureを使って複数のスレッドで共有しています。各スレッドは共有された結果を使って独自の計算を行います。

#include <future>
#include <iostream>
#include <thread>
#include <vector>

int heavy_computation() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate a heavy computation
    return 100; // Return a result
}

void additional_computation(std::shared_future<int> sharedFuture, int threadId) {
    int baseResult = sharedFuture.get();
    int finalResult = baseResult + threadId; // Each thread performs a different computation
    std::cout << "Thread " << threadId << " final result: " << finalResult << std::endl;
}

int main() {
    // Launch the heavy computation asynchronously
    std::future<int> future = std::async(std::launch::async, heavy_computation);
    // Convert the future to a shared_future
    std::shared_future<int> sharedFuture = future.share();

    // Launch multiple threads that perform additional computations
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(additional_computation, sharedFuture, i);
    }

    // Join all threads
    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

コードの解説

  • heavy_computation関数:
  • これは非同期に実行される重い計算タスクをシミュレートしています。この関数は計算結果として100を返します。
  • additional_computation関数:
  • この関数はstd::shared_future<int>を受け取り、共有された結果に基づいて追加の計算を行います。各スレッドは異なる計算(例えば、baseResultにスレッドIDを加算)を行います。
  • main関数:
  • 非同期タスクを起動し、その結果をstd::shared_futureに変換します。複数のスレッドを起動し、それぞれがadditional_computation関数を実行します。全てのスレッドが終了するまで待機します。

この例では、std::shared_futureを使うことで、重い計算の結果を効率的に共有し、複数のスレッドがその結果を利用して追加の作業を行うことができます。これにより、並行処理の効率が向上し、コードの複雑さも軽減されます。

次章では、std::shared_futureを使用する際の注意点と制限について詳しく解説します。

std::shared_futureの注意点と制限

std::shared_futureを使用する際には、いくつかの注意点と制限があります。これらを理解しておくことで、安全かつ効率的にstd::shared_futureを利用することができます。

結果の遅延取得

std::shared_futureは、結果が準備できるまでget()メソッドで呼び出し元をブロックします。したがって、結果が計算中の場合、結果が準備できるまで待機する必要があります。

std::shared_future<int> sharedFuture = future.share();
// この時点では結果はまだ計算中かもしれません
int result = sharedFuture.get(); // 結果が準備できるまで待機します

複数スレッドでのアクセス

std::shared_futureはスレッドセーフですが、同時に多数のスレッドが結果を取得しようとするとパフォーマンスに影響が出る可能性があります。スレッドの数が多すぎる場合、結果取得のために競合が発生することがあります。

例外の再スロー

std::shared_futureは、非同期タスクが例外をスローした場合、その例外を再スローします。したがって、例外処理が必要です。

std::shared_future<int> sharedFuture = future.share();
try {
    int result = sharedFuture.get();
} catch (const std::exception& e) {
    std::cerr << "Exception caught: " << e.what() << std::endl;
}

無効なshared_future

無効なshared_futureオブジェクトは、結果を取得しようとすると未定義の動作を引き起こします。shared_futureが有効かどうかを確認するには、valid()メソッドを使用します。

std::shared_future<int> sharedFuture;
if (sharedFuture.valid()) {
    int result = sharedFuture.get();
} else {
    std::cerr << "Invalid shared_future" << std::endl;
}

参照保持の注意点

std::shared_futureが保持している結果への参照は、そのshared_futureオブジェクトが有効である限り生き続けます。しかし、元のstd::futureが破棄された後にstd::shared_futureを取得しようとすると、結果が未定義になる可能性があります。したがって、std::shared_futureを取得する前にstd::futureが有効であることを確認する必要があります。

リソース管理

std::shared_futureは結果を保持するため、メモリリソースを消費します。大量のshared_futureオブジェクトを使用する場合、メモリ使用量に注意が必要です。

これらの注意点と制限を理解し、適切に対応することで、std::shared_futureを安全かつ効果的に使用することができます。次章では、std::shared_futureを使った応用例について詳しく解説します。

std::shared_futureを使った応用例

ここでは、std::shared_futureの応用例として、より複雑なシナリオでの使用方法を紹介します。具体的には、非同期タスクの結果を使ってさらに別の非同期タスクを連鎖的に実行する方法について解説します。

シナリオ: 分散計算によるデータ処理

以下のシナリオを考えます:

  1. 非同期タスクで大規模なデータを取得する。
  2. 取得したデータを複数のスレッドで並行して処理する。
  3. 各スレッドの結果を集約して最終結果を得る。

コード例

次のコードでは、std::asyncstd::shared_futureを使って、データの取得と並行処理を実装しています。

#include <future>
#include <iostream>
#include <thread>
#include <vector>

// Simulate a function that fetches large data
std::vector<int> fetch_data() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate delay
    return std::vector<int>(100, 1); // Return a vector filled with 1s
}

// Simulate a function that processes a chunk of data
int process_chunk(const std::vector<int>& data, int start, int end) {
    int sum = 0;
    for (int i = start; i < end; ++i) {
        sum += data[i];
    }
    return sum;
}

void process_data(std::shared_future<std::vector<int>> sharedFuture, int start, int end, std::promise<int> promise) {
    const std::vector<int>& data = sharedFuture.get();
    int result = process_chunk(data, start, end);
    promise.set_value(result);
}

int main() {
    // Step 1: Fetch data asynchronously
    std::future<std::vector<int>> futureData = std::async(std::launch::async, fetch_data);
    std::shared_future<std::vector<int>> sharedFutureData = futureData.share();

    // Step 2: Process data in parallel
    int numThreads = 4;
    int chunkSize = 100 / numThreads;
    std::vector<std::thread> threads;
    std::vector<std::promise<int>> promises(numThreads);
    std::vector<std::future<int>> futures;

    for (int i = 0; i < numThreads; ++i) {
        futures.push_back(promises[i].get_future());
        int start = i * chunkSize;
        int end = (i == numThreads - 1) ? 100 : start + chunkSize;
        threads.emplace_back(process_data, sharedFutureData, start, end, std::move(promises[i]));
    }

    // Step 3: Collect results
    int finalResult = 0;
    for (auto& future : futures) {
        finalResult += future.get();
    }

    // Join all threads
    for (auto& thread : threads) {
        thread.join();
    }

    std::cout << "Final result: " << finalResult << std::endl;
    return 0;
}

コードの解説

  • fetch_data関数:
  • 大規模なデータを非同期に取得する関数です。この例では、単に1が100個入ったベクターを返します。
  • process_chunk関数:
  • データの一部(チャンク)を処理する関数です。この例では、データの部分合計を計算します。
  • process_data関数:
  • std::shared_futureを使って取得したデータを処理する関数です。結果をstd::promiseを使って設定します。
  • main関数:
  • 非同期タスクを起動し、その結果をstd::shared_futureに変換します。複数のスレッドを起動してデータのチャンクを並行処理し、各スレッドの結果を集約します。

利点と応用

この例では、std::shared_futureを使って大規模なデータを複数のスレッドで共有し、並行して処理する方法を示しています。これにより、計算の効率化とコードのシンプル化が実現できます。また、この手法は以下のような応用にも適用できます:

  • データ分析:
  • 大規模なデータセットを複数のスレッドで並行して分析する。
  • 画像処理:
  • 画像の各部分を並行して処理し、最終的に結果を統合する。
  • 科学計算:
  • 複数の計算タスクを並行して実行し、結果を集約する。

次章では、std::shared_futureと他の並行処理手法の比較について詳しく解説します。

std::shared_futureとその他の並行処理手法の比較

std::shared_futureはC++の並行処理において強力なツールですが、他にもいくつかの並行処理手法が存在します。ここでは、std::shared_futurestd::promisestd::packaged_task、およびスレッドプールを使用する方法を比較し、それぞれの利点と欠点を説明します。

std::promiseとstd::future

std::promisestd::futureは、値や例外を一方向に転送するための手段です。std::promiseが値を設定し、std::futureがその値を取得します。

#include <future>
#include <iostream>
#include <thread>

void compute(std::promise<int> promise) {
    promise.set_value(42);
}

int main() {
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::thread t(compute, std::move(promise));

    int result = future.get();
    std::cout << "Result: " << result << std::endl;

    t.join();
    return 0;
}
  • 利点: 値の設定と取得が明確に分離されているため、設計がシンプル。
  • 欠点: 値を一度しか取得できないため、複数のスレッドで結果を共有するには向かない。

std::packaged_task

std::packaged_taskは、関数やラムダ式を非同期タスクとして実行し、その結果をstd::futureで取得するための手段です。

#include <future>
#include <iostream>
#include <thread>

int compute() {
    return 42;
}

int main() {
    std::packaged_task<int()> task(compute);
    std::future<int> future = task.get_future();
    std::thread t(std::move(task));

    int result = future.get();
    std::cout << "Result: " << result << std::endl;

    t.join();
    return 0;
}
  • 利点: 非同期タスクの定義と実行を分離できる。
  • 欠点: 値を一度しか取得できないため、複数のスレッドで結果を共有するには向かない。

スレッドプール

スレッドプールは、複数のスレッドを使ってタスクを並行して実行する手法です。タスクのキューを管理し、効率的にスレッドを再利用します。

#include <future>
#include <iostream>
#include <vector>
#include <thread>

class ThreadPool {
public:
    ThreadPool(size_t numThreads);
    template <class F> auto enqueue(F&& f) -> std::future<typename std::result_of<F()>::type>;
    ~ThreadPool();

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

// ThreadPool implementation (omitted for brevity)

int main() {
    ThreadPool pool(4);

    auto result1 = pool.enqueue([] { return 42; });
    auto result2 = pool.enqueue([] { return 24; });

    std::cout << "Result1: " << result1.get() << std::endl;
    std::cout << "Result2: " << result2.get() << std::endl;

    return 0;
}
  • 利点: 複数のタスクを効率的に管理し、スレッドの再利用によるオーバーヘッドの削減。
  • 欠点: 実装が複雑で、タスクのデバッグが難しい場合がある。

比較まとめ

  • std::shared_future:
  • 利点: 複数のスレッドで結果を共有できる。スレッドセーフ。
  • 欠点: 値の取得が遅延する場合がある。
  • std::promiseとstd::future:
  • 利点: 設計がシンプルで、明確な値の設定と取得が可能。
  • 欠点: 結果を一度しか取得できない。
  • std::packaged_task:
  • 利点: 非同期タスクの定義と実行を分離可能。
  • 欠点: 結果を一度しか取得できない。
  • スレッドプール:
  • 利点: タスクの効率的な管理とスレッドの再利用。
  • 欠点: 実装が複雑で、デバッグが難しい。

各手法の特性を理解し、適切な場面で使い分けることで、効率的な並行処理を実現できます。次章では、理解を深めるための演習問題を提供します。

std::shared_futureを利用した演習問題

ここでは、std::shared_futureの理解を深めるための演習問題を提供します。各演習問題には、考慮すべきポイントと、解答例の概要も記載しています。ぜひ挑戦してみてください。

演習問題 1: 並行処理による合計計算

大規模なデータセット(例えば、1から1000までの整数のベクター)を複数のスレッドで分割し、それぞれのスレッドが部分合計を計算し、最終的に全スレッドの結果を集約して全体の合計を求めてください。この過程でstd::shared_futureを使用してデータセットを各スレッドに共有してください。

考慮すべきポイント:

  • データセットの分割方法
  • 各スレッドでの部分合計の計算
  • std::shared_futureを使用してデータセットを共有
  • 部分合計の集約方法

解答例の概要:

  • 非同期タスクでデータセットを生成
  • std::shared_futureを使用してデータセットを共有
  • 各スレッドが部分合計を計算し、その結果をstd::promiseでセット
  • 最終的な合計を集約

演習問題 2: 並列ファイル読み込み

複数の大きなファイルを並行して読み込み、その内容をメモリに保持するプログラムを作成してください。各ファイルの内容をstd::shared_futureを使って読み込み結果を共有し、最終的に全ファイルの内容を連結した結果を出力してください。

考慮すべきポイント:

  • ファイルの並行読み込み
  • std::shared_futureを使用して読み込み結果を共有
  • 読み込み結果の連結方法

解答例の概要:

  • 非同期タスクで各ファイルを読み込み
  • std::shared_futureを使用してファイルの内容を共有
  • 各ファイルの内容を連結し、最終的な結果を出力

演習問題 3: 並行Webスクレイピング

複数のWebページを並行してスクレイピングし、各ページの内容を解析して結果を集約するプログラムを作成してください。std::shared_futureを使用してスクレイピング結果を共有し、解析結果をまとめて出力してください。

考慮すべきポイント:

  • Webページの並行スクレイピング
  • std::shared_futureを使用してスクレイピング結果を共有
  • 解析結果の集約方法

解答例の概要:

  • 非同期タスクで各Webページをスクレイピング
  • std::shared_futureを使用してスクレイピング結果を共有
  • 各ページの解析結果を集約し、最終的な結果を出力

解答例 1: 並行処理による合計計算

以下に、演習問題1の解答例を示します。

#include <future>
#include <iostream>
#include <thread>
#include <vector>
#include <numeric>

std::vector<int> generate_data() {
    std::vector<int> data(1000);
    std::iota(data.begin(), data.end(), 1); // Fill with 1 to 1000
    return data;
}

int partial_sum(const std::vector<int>& data, int start, int end) {
    return std::accumulate(data.begin() + start, data.begin() + end, 0);
}

void compute_partial_sum(std::shared_future<std::vector<int>> sharedFuture, int start, int end, std::promise<int> promise) {
    const std::vector<int>& data = sharedFuture.get();
    int result = partial_sum(data, start, end);
    promise.set_value(result);
}

int main() {
    // Step 1: Generate data asynchronously
    std::future<std::vector<int>> futureData = std::async(std::launch::async, generate_data);
    std::shared_future<std::vector<int>> sharedFutureData = futureData.share();

    // Step 2: Compute partial sums in parallel
    int numThreads = 4;
    int chunkSize = 1000 / numThreads;
    std::vector<std::thread> threads;
    std::vector<std::promise<int>> promises(numThreads);
    std::vector<std::future<int>> futures;

    for (int i = 0; i < numThreads; ++i) {
        futures.push_back(promises[i].get_future());
        int start = i * chunkSize;
        int end = (i == numThreads - 1) ? 1000 : start + chunkSize;
        threads.emplace_back(compute_partial_sum, sharedFutureData, start, end, std::move(promises[i]));
    }

    // Step 3: Collect results
    int finalSum = 0;
    for (auto& future : futures) {
        finalSum += future.get();
    }

    // Join all threads
    for (auto& thread : threads) {
        thread.join();
    }

    std::cout << "Final sum: " << finalSum << std::endl;
    return 0;
}

これらの演習問題に取り組むことで、std::shared_futureの使用方法や並行処理の概念をより深く理解できるでしょう。次章では、std::shared_futureのパフォーマンス最適化について解説します。

std::shared_futureのパフォーマンス最適化

std::shared_futureを使用した並行処理は強力ですが、パフォーマンスを最大限に引き出すためにはいくつかの最適化手法があります。ここでは、std::shared_futureを使用する際のパフォーマンス最適化のためのベストプラクティスを紹介します。

1. 適切なスレッド数の設定

スレッドの数は、システムのハードウェア(特にCPUコア数)に基づいて適切に設定する必要があります。過剰なスレッドはオーバーヘッドを増加させ、パフォーマンスを低下させる可能性があります。

unsigned int numThreads = std::thread::hardware_concurrency();

2. データ分割の工夫

並行処理を行う際のデータ分割は、スレッドごとに均等に分割されるように工夫する必要があります。データ量が均等でないと、スレッドの一部が他のスレッドを待つことになり、パフォーマンスが低下します。

int chunkSize = data.size() / numThreads;

3. 適切な同期の使用

std::shared_futureはスレッドセーフですが、他の共有リソースにアクセスする際には適切な同期手法(ミューテックスや条件変数など)を使用する必要があります。

std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);

4. オーバーヘッドの最小化

std::shared_futureのコピーや、スレッドの生成と終了にはオーバーヘッドが伴います。可能な限りこれらのオーバーヘッドを最小化するために、スレッドプールなどのテクニックを使用することが推奨されます。

// Pseudocode for thread pool
ThreadPool pool(numThreads);
for (int i = 0; i < numThreads; ++i) {
    pool.enqueue(task);
}

5. 非同期タスクの効率的な管理

非同期タスクを効率的に管理するために、std::asyncを使用する際には適切な起動ポリシーを指定することが重要です。std::launch::asyncを指定すると、非同期タスクが確実に新しいスレッドで実行されます。

std::future<int> future = std::async(std::launch::async, task);

6. メモリ管理の最適化

大量のデータを扱う場合、メモリ管理もパフォーマンスに大きな影響を与えます。データを効率的に共有し、不要なコピーを避けるために参照やポインタを適切に使用します。

const std::vector<int>& data = sharedFuture.get();

具体例: パフォーマンス最適化された並行処理

以下の例では、上述の最適化手法を取り入れた並行処理の実装を示します。

#include <future>
#include <iostream>
#include <thread>
#include <vector>
#include <numeric>

std::vector<int> generate_data() {
    std::vector<int> data(1000);
    std::iota(data.begin(), data.end(), 1); // Fill with 1 to 1000
    return data;
}

int partial_sum(const std::vector<int>& data, int start, int end) {
    return std::accumulate(data.begin() + start, data.begin() + end, 0);
}

void compute_partial_sum(std::shared_future<std::vector<int>> sharedFuture, int start, int end, std::promise<int> promise) {
    const std::vector<int>& data = sharedFuture.get();
    int result = partial_sum(data, start, end);
    promise.set_value(result);
}

int main() {
    // Step 1: Generate data asynchronously
    std::future<std::vector<int>> futureData = std::async(std::launch::async, generate_data);
    std::shared_future<std::vector<int>> sharedFutureData = futureData.share();

    // Step 2: Compute partial sums in parallel
    unsigned int numThreads = std::thread::hardware_concurrency();
    int chunkSize = 1000 / numThreads;
    std::vector<std::thread> threads;
    std::vector<std::promise<int>> promises(numThreads);
    std::vector<std::future<int>> futures;

    for (unsigned int i = 0; i < numThreads; ++i) {
        futures.push_back(promises[i].get_future());
        int start = i * chunkSize;
        int end = (i == numThreads - 1) ? 1000 : start + chunkSize;
        threads.emplace_back(compute_partial_sum, sharedFutureData, start, end, std::move(promises[i]));
    }

    // Step 3: Collect results
    int finalSum = 0;
    for (auto& future : futures) {
        finalSum += future.get();
    }

    // Join all threads
    for (auto& thread : threads) {
        thread.join();
    }

    std::cout << "Final sum: " << finalSum << std::endl;
    return 0;
}

この例では、スレッド数をハードウェアに基づいて設定し、データを均等に分割し、std::shared_futureを使って効率的にデータを共有しています。これにより、パフォーマンスの向上とリソースの最適化が実現されています。

次章では、本記事のまとめを行います。

まとめ

本記事では、C++における並行処理の重要性と、std::shared_futureを使った複数スレッドでの結果共有方法について詳しく解説しました。具体的には、std::futureとの違いや基本的な使い方、複数スレッドでの活用方法、さらに応用例やパフォーマンス最適化の手法を紹介しました。

std::shared_futureを利用することで、複数のスレッドが安全かつ効率的に同じ結果を共有でき、非同期タスクの結果を再利用する際に非常に便利です。また、適切なスレッド数の設定やデータ分割、同期手法の選択など、パフォーマンスを最適化するためのベストプラクティスを適用することが重要です。

これらの知識を活用することで、より効果的にC++の並行処理を実現し、高パフォーマンスなアプリケーションを開発することができるでしょう。ぜひ、この記事で紹介した方法を実際のプロジェクトで試してみてください。

コメント

コメントする

目次