C++のマルチスレッド環境での例外処理とエラーハンドリング方法を徹底解説

C++のマルチスレッドプログラミングは、性能向上と効率的なリソース利用を実現するための重要な技術です。しかし、複数のスレッドが同時に実行される環境では、例外処理とエラーハンドリングが特に複雑になります。本記事では、C++のマルチスレッド環境における例外処理とエラーハンドリングの基本から、実践的な方法、ベストプラクティス、応用例までを徹底的に解説します。これにより、安全で信頼性の高いマルチスレッドアプリケーションの開発に役立ててください。

目次

マルチスレッドプログラミングの基本概念

マルチスレッドプログラミングは、1つのアプリケーション内で複数のスレッドを同時に実行することで、CPUの利用効率を最大化し、プログラムのパフォーマンスを向上させる手法です。これにより、I/O操作や計算処理を並列に行うことができ、全体の処理時間を短縮します。C++では、標準ライブラリの<thread>ヘッダーを使用してスレッドを作成および管理します。基本的なマルチスレッドプログラムでは、スレッドの生成、管理、同期を適切に行うことが重要です。次に、スレッドの基本的な操作方法を具体的に見ていきます。

例外処理の基本

C++における例外処理は、エラーが発生したときにプログラムの正常な流れを変更し、適切なエラーハンドリングを行うための重要な手段です。基本的な例外処理の構文は、trycatch、およびthrowを使用します。tryブロック内で発生した例外は、対応するcatchブロックで捕捉されます。例外を投げるには、throwキーワードを使用します。

#include <iostream>
#include <stdexcept>

void exampleFunction() {
    try {
        // 例外を発生させる
        throw std::runtime_error("エラーが発生しました");
    } catch (const std::runtime_error& e) {
        // 例外を捕捉して処理する
        std::cerr << "例外を捕捉: " << e.what() << std::endl;
    }
}

int main() {
    exampleFunction();
    return 0;
}

このコード例では、exampleFunction内でstd::runtime_errorが投げられ、それがcatchブロックで捕捉されます。例外処理を正しく実装することで、プログラムのクラッシュを防ぎ、ユーザーに有用なエラーメッセージを提供することができます。次に、マルチスレッド環境での例外処理の課題について詳しく見ていきます。

マルチスレッド環境での例外処理の課題

マルチスレッド環境における例外処理には、いくつか特有の課題があります。これらの課題を理解し、適切に対処することが重要です。

スレッドごとの例外処理

各スレッドは独自の実行コンテキストを持っているため、一つのスレッドで発生した例外は他のスレッドには影響を与えません。これにより、例外が発生したスレッド内でしか例外処理が行われず、全体のエラーハンドリングが複雑になります。

例外の伝播

例外はスレッドの境界を越えて自動的に伝播しません。スレッド内で例外が発生しても、そのスレッドの外部には通知されないため、例外を適切に伝播させるためのメカニズムを自分で実装する必要があります。

スレッドの終了

例外がスローされるとスレッドは即座に終了します。これにより、リソースリークや未完了のタスクが発生する可能性があります。スレッドの終了時にリソースを適切に解放するための処理を確実に行う必要があります。

同期の問題

例外が発生すると、ロックやミューテックスなどの同期オブジェクトが適切に解放されない場合があります。これによりデッドロックやリソース競合が発生するリスクが高まります。

これらの課題に対処するためには、スレッド間で例外を伝播させる方法や、スレッドセーフなエラーハンドリングの実装が必要です。次に、スレッド間での例外の伝播方法について詳しく見ていきます。

スレッド間での例外の伝播

マルチスレッド環境で例外を適切に伝播させるためには、スレッド間で情報を共有するメカニズムを導入する必要があります。C++では、標準ライブラリの機能を利用して、例外をスレッド間で伝播させる方法があります。

std::futureとstd::promiseを使用する方法

std::futurestd::promiseを使用することで、スレッド間での例外の伝播を実現できます。std::promiseを使って例外をスレッドに設定し、std::futureでその例外を取得します。

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

void threadFunction(std::promise<void>& prom) {
    try {
        // 何かの処理
        throw std::runtime_error("スレッド内で例外が発生しました");
    } catch (...) {
        // 例外をpromiseに設定
        prom.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<void> prom;
    std::future<void> fut = prom.get_future();
    std::thread t(threadFunction, std::ref(prom));

    try {
        // futureの結果を取得し例外を再スロー
        fut.get();
    } catch (const std::exception& e) {
        std::cerr << "例外を捕捉: " << e.what() << std::endl;
    }

    t.join();
    return 0;
}

この例では、threadFunction内で例外が発生すると、その例外はstd::promiseを通じてメインスレッドに伝播されます。メインスレッドでは、std::futureを使用して例外を取得し、適切に処理します。

独自の例外伝播メカニズム

std::futurestd::promiseを使用しない場合、独自の例外伝播メカニズムを実装することも可能です。例えば、例外情報を格納する共有オブジェクトを使用し、スレッド間で状態を監視する方法があります。

これにより、スレッド間で例外が発生した場合に適切に伝播させることができます。次に、スレッド安全なエラーハンドリングの方法について説明します。

スレッド安全なエラーハンドリング

マルチスレッド環境でのエラーハンドリングは、スレッド間の安全性と同期を考慮する必要があります。以下に、スレッド安全なエラーハンドリングの方法をいくつか紹介します。

ミューテックスとロックを使用する

スレッド間で共有されるリソースへのアクセスを同期するために、ミューテックス(mutex)とロック(lock)を使用します。これにより、同時アクセスによる競合やデッドロックを防ぎます。

#include <iostream>
#include <thread>
#include <mutex>
#include <stdexcept>

std::mutex mtx;

void safeFunction() {
    std::lock_guard<std::mutex> lock(mtx);  // 自動的にロックが解放される
    // 何かの処理
    throw std::runtime_error("スレッド内で例外が発生しました");
}

int main() {
    try {
        std::thread t1(safeFunction);
        std::thread t2(safeFunction);
        t1.join();
        t2.join();
    } catch (const std::exception& e) {
        std::cerr << "例外を捕捉: " << e.what() << std::endl;
    }

    return 0;
}

この例では、std::lock_guardを使用してミューテックスを管理し、例外が発生しても自動的にロックが解放されるようにしています。

スレッドローカルストレージを使用する

スレッドローカルストレージ(thread-local storage, TLS)を使用すると、各スレッドごとに独立したデータを保持できます。これにより、スレッド間のデータ競合を防ぎます。

#include <iostream>
#include <thread>

thread_local int threadData = 0;

void threadFunction(int id) {
    threadData = id;
    std::cout << "スレッド " << id << " のデータ: " << threadData << std::endl;
}

int main() {
    std::thread t1(threadFunction, 1);
    std::thread t2(threadFunction, 2);

    t1.join();
    t2.join();

    return 0;
}

この例では、各スレッドが独自のthreadDataを持ち、他のスレッドのデータと競合しません。

スレッドプールを利用する

スレッドプールを利用すると、一定数のスレッドを管理し、タスクを効率的に分配できます。これにより、スレッドの生成と破棄のオーバーヘッドを削減し、エラーハンドリングも一元化しやすくなります。

#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <functional>
#include <future>
#include <stdexcept>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

inline ThreadPool::ThreadPool(size_t threads)
    : stop(false) {
    for(size_t i = 0; i < threads; ++i)
        workers.emplace_back(
            [this] {
                for(;;) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            }
        );
}

template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
    -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared<std::packaged_task<return_type()>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );

    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}

inline ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

int main() {
    ThreadPool pool(4);
    auto result = pool.enqueue([]() -> int {
        throw std::runtime_error("タスク内で例外が発生しました");
        return 42;
    });

    try {
        std::cout << result.get() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "例外を捕捉: " << e.what() << std::endl;
    }

    return 0;
}

この例では、スレッドプールを使用してタスクを管理し、例外が発生した場合はメインスレッドで適切にハンドリングします。

これらの方法を組み合わせて使用することで、スレッド安全なエラーハンドリングを実現し、マルチスレッドプログラムの信頼性と安全性を向上させることができます。次に、実践例として簡単なマルチスレッドプログラムを紹介します。

実践例:簡単なマルチスレッドプログラム

ここでは、マルチスレッドプログラミングの基本的な例として、スレッドを使用して並列にタスクを実行する簡単なプログラムを紹介します。この例では、複数のスレッドがそれぞれ独立して動作し、並行して計算を行います。

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

void threadFunction(int threadID) {
    std::cout << "スレッド " << threadID << " が実行中です。" << std::endl;
    // ここで何らかの計算や処理を実行
    for (int i = 0; i < 5; ++i) {
        std::cout << "スレッド " << threadID << " のカウント: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    std::cout << "スレッド " << threadID << " が終了しました。" << std::endl;
}

int main() {
    const int numThreads = 3;
    std::vector<std::thread> threads;

    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(threadFunction, i);
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "全てのスレッドが終了しました。" << std::endl;
    return 0;
}

このプログラムでは、以下の手順でマルチスレッドの処理を行います:

  1. threadFunctionという関数を定義し、各スレッドが実行する処理を記述します。この関数は、スレッドIDを引数に取り、スレッドごとに異なるメッセージを表示します。
  2. main関数内で、3つのスレッドを生成します。各スレッドはthreadFunctionを実行し、スレッドIDを渡します。
  3. std::threadオブジェクトをstd::vectorに格納し、スレッドを管理します。
  4. joinメソッドを使用して、すべてのスレッドが終了するのを待ちます。

この簡単な例では、各スレッドが独立して動作し、並列に処理を行います。次に、このプログラムに例外処理を追加する方法について説明します。

実践例:例外処理を追加する

前述の簡単なマルチスレッドプログラムに例外処理を追加して、スレッド内で発生した例外を適切に捕捉し、スレッド間で伝播させる方法を示します。ここでは、std::promisestd::futureを使用して、例外をメインスレッドに伝播させます。

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

void threadFunction(int threadID, std::promise<void>& prom) {
    try {
        std::cout << "スレッド " << threadID << " が実行中です。" << std::endl;
        // 例外を発生させる
        if (threadID == 1) {
            throw std::runtime_error("スレッド " + std::to_string(threadID) + " で例外が発生しました。");
        }
        for (int i = 0; i < 5; ++i) {
            std::cout << "スレッド " << threadID << " のカウント: " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        std::cout << "スレッド " << threadID << " が終了しました。" << std::endl;
        prom.set_value();
    } catch (...) {
        prom.set_exception(std::current_exception());
    }
}

int main() {
    const int numThreads = 3;
    std::vector<std::thread> threads;
    std::vector<std::promise<void>> promises(numThreads);
    std::vector<std::future<void>> futures;

    for (int i = 0; i < numThreads; ++i) {
        futures.push_back(promises[i].get_future());
        threads.emplace_back(threadFunction, i, std::ref(promises[i]));
    }

    for (int i = 0; i < numThreads; ++i) {
        try {
            futures[i].get();
        } catch (const std::exception& e) {
            std::cerr << "例外を捕捉: " << e.what() << std::endl;
        }
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "全てのスレッドが終了しました。" << std::endl;
    return 0;
}

この改良版プログラムでは、以下の手順で例外処理を追加しています:

  1. 各スレッドに対してstd::promise<void>を渡し、スレッド内で例外が発生した場合に例外を設定する。
  2. threadFunction内で例外が発生した場合、std::promiseを通じて例外をメインスレッドに伝播させる。
  3. メインスレッドでは、各スレッドのstd::futureを使用して、例外を取得し適切にハンドリングする。

このようにして、スレッド内で発生した例外を安全にキャッチし、メインスレッドで処理することができます。次に、マルチスレッド環境での例外処理とエラーハンドリングのベストプラクティスについて説明します。

ベストプラクティス

マルチスレッド環境での例外処理とエラーハンドリングを効果的に行うためには、いくつかのベストプラクティスに従うことが重要です。以下に、その主なポイントを紹介します。

1. 例外の早期捕捉と報告

例外はできるだけ早期に捕捉し、適切に報告することが重要です。スレッド内で例外が発生した場合、すぐにメインスレッドに伝播させ、ユーザーに対して有用なエラーメッセージを提供します。

try {
    // 例外を投げる可能性のあるコード
} catch (const std::exception& e) {
    std::cerr << "エラー: " << e.what() << std::endl;
    // 必要に応じてログファイルにも記録する
}

2. スレッドセーフなリソース管理

マルチスレッド環境では、共有リソースへのアクセスを適切に同期することが必要です。std::mutexstd::lock_guardなどの同期プリミティブを利用して、リソース競合を防ぎます。

std::mutex mtx;
void safeFunction() {
    std::lock_guard<std::mutex> lock(mtx);
    // 共有リソースへのアクセス
}

3. リソースの適切な解放

スレッドが例外で終了する場合でも、リソースリークを防ぐために、確実にリソースを解放することが重要です。RAII(Resource Acquisition Is Initialization)パターンを使用すると、リソースの自動解放が保証されます。

class Resource {
public:
    Resource() { /* リソースの初期化 */ }
    ~Resource() { /* リソースの解放 */ }
};

4. スレッドローカルストレージの活用

スレッドごとに独立したデータが必要な場合、スレッドローカルストレージ(TLS)を使用します。これにより、スレッド間のデータ競合を防ぐことができます。

thread_local int threadData = 0;

5. スレッドプールの利用

スレッドプールを使用すると、スレッドの生成と破棄のオーバーヘッドを削減し、タスクの効率的な管理が可能になります。また、例外処理も一元化しやすくなります。

ThreadPool pool(4);
auto result = pool.enqueue([]() -> int {
    // タスク処理
    return 42;
});

6. 詳細なログとモニタリング

エラーハンドリングの一環として、詳細なログを記録し、システムの状態を常にモニタリングします。これにより、問題の早期発見と迅速な対応が可能になります。

std::cerr << "エラーメッセージ" << std::endl;
// ログファイルにも同様のメッセージを記録

これらのベストプラクティスを遵守することで、マルチスレッド環境における例外処理とエラーハンドリングの信頼性と安全性を向上させることができます。次に、大規模プロジェクトでの実装例を紹介します。

応用例:大規模プロジェクトでの実装

大規模プロジェクトでは、マルチスレッド環境での例外処理とエラーハンドリングがさらに重要となります。ここでは、大規模なシステムでの実装例として、例外処理とエラーハンドリングの具体的な方法を紹介します。

例:Webサーバーの並行処理

大規模なWebサーバーアプリケーションでは、多数のクライアントからのリクエストを並行して処理する必要があります。以下の例では、スレッドプールを使用してリクエストを処理し、例外処理を行います。

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <future>
#include <stdexcept>
#include <mutex>
#include <condition_variable>

// スレッドプールクラスの定義
class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

inline ThreadPool::ThreadPool(size_t threads)
    : stop(false) {
    for(size_t i = 0; i < threads; ++i)
        workers.emplace_back(
            [this] {
                for(;;) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            }
        );
}

template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared<std::packaged_task<return_type()>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );

    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}

inline ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

// Webサーバーのリクエスト処理関数
void handleRequest(int requestID) {
    try {
        std::cout << "リクエスト " << requestID << " を処理中です。" << std::endl;
        // ここでリクエスト処理を実行
        if (requestID % 2 == 0) {
            throw std::runtime_error("リクエスト " + std::to_string(requestID) + " で例外が発生しました。");
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::cout << "リクエスト " << requestID << " の処理が完了しました。" << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "例外を捕捉: " << e.what() << std::endl;
    }
}

int main() {
    const int numRequests = 10;
    ThreadPool pool(4);

    for (int i = 0; i < numRequests; ++i) {
        pool.enqueue(handleRequest, i);
    }

    return 0;
}

この例では、以下の手順で大規模プロジェクトの例外処理とエラーハンドリングを実装しています:

  1. ThreadPoolクラスを使用してスレッドプールを管理し、タスクを効率的に分配します。
  2. handleRequest関数でリクエストを処理し、例外が発生した場合はtry-catchブロックで例外を捕捉します。
  3. メインスレッドでは、スレッドプールにリクエスト処理タスクを追加し、スレッド間での例外処理を適切に行います。

この方法により、Webサーバーのような大規模なシステムでも、効率的かつ安全にリクエストを処理し、例外を適切にハンドリングできます。

次に、理解を深めるための演習問題を提示します。

演習問題

以下の演習問題を通じて、C++のマルチスレッド環境での例外処理とエラーハンドリングの理解を深めましょう。各問題には実装例を提供しますので、自分の環境で実行し、動作を確認してください。

演習1:基本的な例外処理の追加

以下のプログラムに例外処理を追加し、スレッド内で例外が発生した場合に適切に処理するように修正してください。

#include <iostream>
#include <thread>

void threadFunction(int threadID) {
    std::cout << "スレッド " << threadID << " が実行中です。" << std::endl;
    // ここに例外を発生させるコードを追加
    if (threadID == 2) {
        // 例外を投げる
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "スレッド " << threadID << " が終了しました。" << std::endl;
}

int main() {
    const int numThreads = 3;
    std::thread threads[numThreads];

    for (int i = 0; i < numThreads; ++i) {
        threads[i] = std::thread(threadFunction, i);
    }

    for (int i = 0; i < numThreads; ++i) {
        threads[i].join();
    }

    std::cout << "全てのスレッドが終了しました。" << std::endl;
    return 0;
}

演習2:std::promiseとstd::futureを使用した例外伝播

以下のプログラムにstd::promisestd::futureを使用して、スレッド間で例外を伝播させるように修正してください。

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

void threadFunction(int threadID, std::promise<void>& prom) {
    try {
        std::cout << "スレッド " << threadID << " が実行中です。" << std::endl;
        // ここに例外を発生させるコードを追加
        if (threadID == 2) {
            throw std::runtime_error("スレッド " + std::to_string(threadID) + " で例外が発生しました。");
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::cout << "スレッド " << threadID << " が終了しました。" << std::endl;
        prom.set_value();
    } catch (...) {
        prom.set_exception(std::current_exception());
    }
}

int main() {
    const int numThreads = 3;
    std::vector<std::thread> threads;
    std::vector<std::promise<void>> promises(numThreads);
    std::vector<std::future<void>> futures;

    for (int i = 0; i < numThreads; ++i) {
        futures.push_back(promises[i].get_future());
        threads.emplace_back(threadFunction, i, std::ref(promises[i]));
    }

    for (int i = 0; i < numThreads; ++i) {
        try {
            futures[i].get();
        } catch (const std::exception& e) {
            std::cerr << "例外を捕捉: " << e.what() << std::endl;
        }
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "全てのスレッドが終了しました。" << std::endl;
    return 0;
}

演習3:スレッドプールの実装

スレッドプールを使用して、複数のタスクを効率的に処理するプログラムを作成してください。以下の基本的なスレッドプールクラスを使用し、例外処理を適切に実装してください。

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <future>
#include <stdexcept>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

inline ThreadPool::ThreadPool(size_t threads)
    : stop(false) {
    for(size_t i = 0; i < threads; ++i)
        workers.emplace_back(
            [this] {
                for(;;) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            }
        );
}

template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared<std::packaged_task<return_type()>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );

    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}

inline ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

void taskFunction(int taskID) {
    std::cout << "タスク " << taskID << " を処理中です。" << std::endl;
    if (taskID % 2 == 0) {
        throw std::runtime_error("タスク " + std::to_string(taskID) + " で例外が発生しました。");
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "タスク " << taskID << " の処理が完了しました。" << std::endl;
}

int main() {
    const int numTasks = 10;
    ThreadPool pool(4);

    for (int i = 0; i < numTasks; ++i) {
        pool.enqueue(taskFunction, i);
    }

    return 0;
}

これらの演習問題を通じて、マルチスレッド環境での例外処理とエラーハンドリングの技術を深く理解し、実践的なスキルを身につけてください。次に、本記事の要点をまとめます。

まとめ

本記事では、C++のマルチスレッド環境での例外処理とエラーハンドリングについて解説しました。まず、マルチスレッドプログラミングの基本概念を理解し、次に例外処理の基本について学びました。マルチスレッド環境における特有の課題についても触れ、例外の伝播方法やスレッド安全なエラーハンドリングの方法を実践例を交えて説明しました。また、大規模プロジェクトでの実装例を通じて、応用的なアプローチを紹介し、演習問題で理解を深めました。

これらの知識を活用して、複雑なマルチスレッドアプリケーションにおける例外処理とエラーハンドリングを効果的に行い、安全で信頼性の高いプログラムを開発してください。

コメント

コメントする

目次