C++のマルチスレッドとデザインパターンの具体例と実装方法

C++におけるマルチスレッドプログラミングは、コンピュータのパフォーマンスを最大限に引き出すための重要な技術です。特に、複雑な並行処理を効率的に行うためには、デザインパターンを活用することが不可欠です。本記事では、C++のマルチスレッドの基本概念から始め、アクターパターンやタスクキューパターンなどのデザインパターンについて、具体例とともに詳しく解説します。これにより、より効率的でスケーラブルなプログラムを設計・実装できるようになることを目指します。

目次

マルチスレッドの基本概念

マルチスレッドプログラミングは、一つのプログラム内で複数のスレッド(軽量プロセス)を同時に実行する手法です。これにより、プログラムのパフォーマンスを向上させることができます。マルチスレッドの利点には以下の点があります。

パフォーマンスの向上

マルチスレッドを使用することで、複数のタスクを並行して実行できるため、CPUの利用率を最大化し、プログラムの応答性を改善することができます。

効率的なリソース使用

複数のスレッドが同じメモリ空間を共有するため、リソースの使用が効率化されます。これにより、メモリ消費を抑えることが可能です。

リアルタイム処理の実現

リアルタイムシステムにおいて、マルチスレッドは必須の技術です。異なるタスクを同時に処理することで、即時応答が求められるアプリケーションでの使用が可能になります。

このように、マルチスレッドプログラミングは、C++におけるパフォーマンス向上と効率的なリソース管理を実現するための重要な技術となります。

C++でのスレッド生成と管理

C++では、標準ライブラリを使用してスレッドを生成し、管理することができます。以下に、基本的なスレッドの生成と管理の方法を具体例とともに説明します。

スレッドの生成

C++11以降では、std::threadクラスを使用して簡単にスレッドを生成できます。以下は、スレッドを生成して実行する基本的な例です。

#include <iostream>
#include <thread>

// スレッドで実行する関数
void threadFunction() {
    std::cout << "スレッドが実行されています" << std::endl;
}

int main() {
    // スレッドの生成
    std::thread myThread(threadFunction);

    // メインスレッドでの処理
    std::cout << "メインスレッドが実行されています" << std::endl;

    // スレッドの終了を待機
    myThread.join();

    return 0;
}

このプログラムでは、threadFunctionを実行する新しいスレッドが生成され、メインスレッドと並行して動作します。

スレッドの管理

スレッドを生成した後、以下の方法で管理することができます。

スレッドの結合

スレッドが終了するのを待つには、joinメソッドを使用します。これにより、生成したスレッドが完了するまでメインスレッドが待機します。

スレッドの分離

detachメソッドを使用すると、スレッドを分離してバックグラウンドで実行させることができます。分離されたスレッドは独立して動作し、メインスレッドの終了を待ちません。

#include <iostream>
#include <thread>
#include <chrono>

void threadFunction() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "バックグラウンドスレッドが終了しました" << std::endl;
}

int main() {
    std::thread myThread(threadFunction);
    myThread.detach();

    std::cout << "メインスレッドが終了します" << std::endl;

    // メインスレッドがすぐに終了しても、バックグラウンドスレッドは動作を続けます。
    return 0;
}

これにより、メインスレッドが終了した後も、バックグラウンドスレッドが独立して動作を続けます。スレッドの適切な管理は、プログラムの信頼性と安定性を確保するために重要です。

スレッド間通信の手法

マルチスレッドプログラミングでは、スレッド間の通信と同期が重要です。主に以下の二つの手法が用いられます。

メッセージパッシング

メッセージパッシングは、スレッド間でデータを共有せずにメッセージを送受信する方法です。この方法では、スレッド間のデータ競合を防ぐことができます。C++では、std::queueと条件変数を組み合わせて実装することが一般的です。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queue<int> messageQueue;
std::mutex mtx;
std::condition_variable cv;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        messageQueue.push(i);
        cv.notify_one();
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !messageQueue.empty(); });
        int message = messageQueue.front();
        messageQueue.pop();
        std::cout << "Consumed: " << message << std::endl;
        if (message == 9) break; // 終了条件
    }
}

int main() {
    std::thread prodThread(producer);
    std::thread consThread(consumer);

    prodThread.join();
    consThread.join();

    return 0;
}

このプログラムでは、プロデューサースレッドがメッセージをキューに追加し、コンシューマースレッドがそのメッセージを処理します。条件変数を使用してスレッド間の同期を実現しています。

共有メモリ

共有メモリは、複数のスレッドが同じメモリ領域にアクセスする方法です。データ競合を防ぐために、適切な同期機構を使用する必要があります。C++では、std::mutexstd::lock_guardを用いて実装します。

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

int sharedCounter = 0;
std::mutex mtx;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++sharedCounter;
    }
}

int main() {
    std::thread thread1(increment);
    std::thread thread2(increment);

    thread1.join();
    thread2.join();

    std::cout << "Final counter value: " << sharedCounter << std::endl;

    return 0;
}

このプログラムでは、std::mutexを使用して複数のスレッドが共有変数sharedCounterに安全にアクセスできるようにしています。std::lock_guardを使用することで、ロックの取得と解放を自動的に行います。

以上のように、スレッド間通信の手法にはそれぞれ利点と適用シーンがあり、適切な手法を選択することが重要です。

アクターパターンの基本

アクターパターンは、並行プログラミングにおけるデザインパターンの一つであり、アクターと呼ばれる独立したコンポーネントを使用して並行処理を管理します。アクターはメッセージを受信し、それに応じた処理を行います。アクターパターンの主な特徴と利点について説明します。

アクターの基本概念

アクターは次の三つの操作を行います:

  • メッセージの受信と処理
  • 新しいアクターの生成
  • 他のアクターへのメッセージ送信

アクターは他のアクターとメッセージを交換することによって通信し、直接的な共有メモリを持ちません。この非共有メモリのアプローチにより、データ競合やロックの問題を回避できます。

アクターパターンの利点

  1. スケーラビリティ:
    • アクターは独立して動作し、必要に応じて動的に生成・削除できるため、システムのスケーラビリティが高まります。
  2. デッドロック回避:
    • アクター同士がメッセージで非同期に通信するため、デッドロックのリスクが低くなります。
  3. エラーハンドリング:
    • 各アクターが独立して動作するため、あるアクターがエラーを起こしても他のアクターに影響を与えません。これにより、システム全体の堅牢性が向上します。

アクターの構成要素

  1. メッセージ:
    • アクターが通信するための手段であり、メッセージにはアクター間で必要なデータや指示が含まれます。
  2. アクターモデル:
    • アクターは、メッセージの受信、処理、他のアクターへのメッセージ送信などを管理します。
  3. メッセージキュー:
    • 受信したメッセージを順番に処理するためのキューです。

アクターパターンは、特に大規模な並行処理が必要なシステムやリアルタイムシステムに適しています。このパターンを理解し適用することで、より効率的で拡張性の高い並行プログラミングを実現できます。

C++でのアクターパターン実装

C++でアクターパターンを実装するためには、アクター、メッセージ、メッセージキューなどの基本コンポーネントを構築する必要があります。以下に、具体的な実装例を示します。

アクタークラスの定義

まず、アクタークラスを定義します。このクラスは、メッセージを受信し処理する基本的な機能を持ちます。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <functional>

// メッセージの型定義
using Message = std::function<void()>;

// アクタークラスの定義
class Actor {
public:
    Actor() : stop(false) {
        worker = std::thread([this]() { this->processMessages(); });
    }

    ~Actor() {
        {
            std::unique_lock<std::mutex> lock(mtx);
            stop = true;
        }
        cv.notify_all();
        worker.join();
    }

    void send(Message msg) {
        {
            std::unique_lock<std::mutex> lock(mtx);
            messageQueue.push(msg);
        }
        cv.notify_one();
    }

private:
    void processMessages() {
        while (true) {
            Message msg;
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this] { return !messageQueue.empty() || stop; });
                if (stop && messageQueue.empty()) break;
                msg = messageQueue.front();
                messageQueue.pop();
            }
            msg();
        }
    }

    std::queue<Message> messageQueue;
    std::mutex mtx;
    std::condition_variable cv;
    std::thread worker;
    bool stop;
};

このActorクラスは、メッセージをキューに追加し、それを処理するための基本的な機能を提供します。

アクターの使用例

次に、上記のアクタークラスを使って具体的なアクターを作成し、メッセージを送信する例を示します。

#include <iostream>
#include <string>

// 具体的なアクタークラス
class PrintActor : public Actor {
public:
    void printMessage(const std::string& message) {
        send([message]() {
            std::cout << "Message: " << message << std::endl;
        });
    }
};

int main() {
    PrintActor actor;
    actor.printMessage("Hello, Actor!");
    actor.printMessage("This is an example of Actor Pattern.");

    // 少し待ってから終了することでメッセージの処理が行われる
    std::this_thread::sleep_for(std::chrono::seconds(1));

    return 0;
}

この例では、PrintActorクラスを定義し、printMessageメソッドを通じてメッセージをアクターに送信します。アクターは独立したスレッドでメッセージを処理し、メッセージ内容をコンソールに出力します。

アクターパターンの拡張

アクターパターンは、さらに複雑なシナリオにも拡張できます。例えば、複数のアクター間でメッセージをやり取りするシステムを構築することで、並行処理を効率化できます。

#include <iostream>

class SumActor : public Actor {
public:
    void addNumber(int number) {
        send([this, number]() {
            sum += number;
            std::cout << "Current Sum: " << sum << std::endl;
        });
    }

private:
    int sum = 0;
};

int main() {
    SumActor actor;
    actor.addNumber(10);
    actor.addNumber(20);
    actor.addNumber(30);

    // 少し待ってから終了することでメッセージの処理が行われる
    std::this_thread::sleep_for(std::chrono::seconds(1));

    return 0;
}

この例では、SumActorクラスを作成し、数値を追加して累積和を計算する機能を持たせています。アクターパターンを活用することで、並行処理が必要なプログラムをより効率的かつ効果的に実装できます。

タスクキューパターンの基本

タスクキューパターンは、並行処理の管理において広く使用されるデザインパターンの一つです。タスクキューは、実行すべきタスクをキューに蓄積し、ワーカーがそれを逐次処理する仕組みを提供します。このパターンの主な特徴と利点について説明します。

タスクキューの基本概念

タスクキューは以下の要素で構成されます:

  • タスク: 実行するべき具体的な処理や作業。タスクは関数オブジェクトやラムダ式として定義されることが多いです。
  • キュー: タスクを順番に保持するデータ構造。通常はFIFO(先入れ先出し)で管理されます。
  • ワーカー: キューからタスクを取り出して実行するスレッド。

タスクキューは、複数のワーカーが同時にタスクを処理することで、並行処理を効果的に管理します。

タスクキューパターンの利点

  1. スケーラビリティ:
    • タスクキューを使用すると、ワーカーの数を増減させることで、システムの負荷に応じたスケーラビリティが実現できます。
  2. 効率的なリソース使用:
    • タスクをキューに蓄積し、空いているワーカーがそれを処理するため、リソースの効率的な利用が可能です。
  3. シンプルな設計:
    • タスクの生成と処理が明確に分離されているため、システム設計がシンプルかつ明瞭になります。

タスクキューの構成要素

  1. タスク:
    • 実行するべき処理を関数オブジェクトやラムダ式で定義します。
  2. タスクキュー:
    • タスクを順番に保持するデータ構造であり、通常はスレッドセーフなキューが使用されます。
  3. ワーカー:
    • タスクキューからタスクを取り出して実行するスレッドです。通常、複数のワーカーが並行して動作します。

タスクキューパターンは、特に大量のタスクを並行して処理する必要があるシステムや、負荷に応じて動的にスケールさせたいシステムに適しています。このパターンを理解し、適用することで、効率的でスケーラブルな並行処理システムを構築することが可能です。

C++でのタスクキューパターン実装

C++でタスクキューパターンを実装するには、タスクをキューに蓄積し、ワーカーがそれを逐次処理する仕組みを構築する必要があります。以下に具体的な実装例を示します。

タスクキュークラスの定義

まず、スレッドセーフなタスクキュークラスを定義します。このクラスは、タスクの追加と取得を管理します。

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>

class TaskQueue {
public:
    using Task = std::function<void()>;

    void push(Task task) {
        {
            std::unique_lock<std::mutex> lock(mtx);
            tasks.push(task);
        }
        cv.notify_one();
    }

    Task pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return !tasks.empty(); });
        Task task = tasks.front();
        tasks.pop();
        return task;
    }

private:
    std::queue<Task> tasks;
    std::mutex mtx;
    std::condition_variable cv;
};

このTaskQueueクラスは、タスクの追加と取得を行い、条件変数を使ってタスクが追加されるまでワーカーを待機させます。

ワーカークラスの定義

次に、ワーカーを定義します。ワーカーはタスクキューからタスクを取り出して実行します。

class Worker {
public:
    Worker(TaskQueue& queue) : taskQueue(queue), stop(false) {
        workerThread = std::thread([this]() { this->run(); });
    }

    ~Worker() {
        stop = true;
        taskQueue.push([]{}); // ダミータスクでスレッドを起こす
        workerThread.join();
    }

private:
    void run() {
        while (!stop) {
            TaskQueue::Task task = taskQueue.pop();
            task();
        }
    }

    TaskQueue& taskQueue;
    std::thread workerThread;
    bool stop;
};

このWorkerクラスは、タスクキューからタスクを取り出して実行し、終了時にはスレッドを正しく停止させます。

タスクキューパターンの使用例

最後に、タスクキューとワーカーを使用して具体的なタスクを処理する例を示します。

int main() {
    TaskQueue taskQueue;

    // ワーカーを複数生成
    std::vector<std::unique_ptr<Worker>> workers;
    for (int i = 0; i < 4; ++i) {
        workers.emplace_back(std::make_unique<Worker>(taskQueue));
    }

    // タスクを追加
    for (int i = 0; i < 10; ++i) {
        taskQueue.push([i]() {
            std::cout << "タスク " << i << " を実行中" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }

    // ワーカーがタスクをすべて処理するまで待機
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

この例では、4つのワーカーを生成し、10個のタスクをタスクキューに追加しています。ワーカーはキューからタスクを取り出して並行して処理します。

タスクキューパターンを使用することで、複数のタスクを効率的に並行処理することが可能です。この実装例を基に、さらに複雑なシステムにも応用することができます。

デザインパターンの組み合わせ

アクターパターンとタスクキューパターンを組み合わせることで、さらに強力で柔軟な並行処理システムを構築することができます。これにより、各アクターが独立して動作しつつ、タスクキューを介して効率的にタスクを分配することができます。以下にその具体例を示します。

システム設計の例

以下の例では、複数のアクターが存在し、各アクターはタスクキューを使用してタスクを処理します。アクターは、独自のタスクキューを持ち、他のアクターと通信することでタスクを分担します。

アクタークラスの修正

各アクターが独自のタスクキューを持つように、アクタークラスを修正します。

class Actor {
public:
    Actor() : stop(false) {
        worker = std::thread([this]() { this->processMessages(); });
    }

    ~Actor() {
        {
            std::unique_lock<std::mutex> lock(mtx);
            stop = true;
        }
        taskQueue.push([]{});
        worker.join();
    }

    void send(TaskQueue::Task task) {
        taskQueue.push(task);
    }

private:
    void processMessages() {
        while (true) {
            TaskQueue::Task task = taskQueue.pop();
            if (stop && taskQueue.empty()) break;
            task();
        }
    }

    TaskQueue taskQueue;
    std::mutex mtx;
    std::thread worker;
    bool stop;
};

アクター間の通信

アクター同士がメッセージを交換し、タスクを処理する具体例を示します。例えば、プロデューサーアクターがタスクを生成し、コンシューマーアクターがそれを処理するシステムです。

#include <iostream>
#include <vector>
#include <memory>

class ProducerActor : public Actor {
public:
    void produce(int count, Actor& consumer) {
        for (int i = 0; i < count; ++i) {
            send([i, &consumer]() {
                std::cout << "Producing task " << i << std::endl;
                consumer.send([i]() {
                    std::cout << "Consuming task " << i << std::endl;
                });
            });
        }
    }
};

class ConsumerActor : public Actor {
public:
    void consume(TaskQueue::Task task) {
        send(task);
    }
};

int main() {
    ProducerActor producer;
    ConsumerActor consumer;

    producer.produce(10, consumer);

    // 少し待ってから終了することでメッセージの処理が行われる
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

この例では、ProducerActorがタスクを生成し、そのタスクをConsumerActorに送信します。ConsumerActorは受信したタスクを処理します。

システムの利点

  • 高い並行性: 各アクターが独立して動作し、タスクキューを通じて効率的にタスクを分配するため、システム全体の並行性が向上します。
  • 柔軟性と拡張性: アクターを追加・削除することで、システムの負荷に応じて動的にスケーリングが可能です。
  • エラーハンドリングの強化: 各アクターが独立して動作するため、特定のアクターがエラーを起こしても他のアクターに影響を与えません。

このように、アクターパターンとタスクキューパターンを組み合わせることで、より強力で効率的な並行処理システムを構築することができます。この手法は、複雑なリアルタイムシステムや大規模な分散システムにも適用可能です。

応用例: リアルタイムシステムでの使用

リアルタイムシステムでは、タスクを迅速かつ確実に処理する必要があります。ここでは、C++でのマルチスレッドプログラミングとデザインパターンを活用したリアルタイムシステムの応用例を紹介します。

リアルタイムデータ処理システム

リアルタイムデータ処理システムでは、センサーデータの収集と解析、アクチュエーターの制御など、複数のタスクを並行して処理する必要があります。以下に、アクターパターンとタスクキューパターンを組み合わせた具体例を示します。

センサーアクター

センサーアクターは、定期的にセンサーデータを収集し、データ解析アクターに送信します。

#include <iostream>
#include <chrono>

class SensorActor : public Actor {
public:
    void startCollecting(Actor& dataProcessor) {
        send([this, &dataProcessor]() {
            while (true) {
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                int data = collectData();
                dataProcessor.send([data]() {
                    processData(data);
                });
            }
        });
    }

private:
    int collectData() {
        // センサーデータの模擬収集
        static int counter = 0;
        return ++counter;
    }

    static void processData(int data) {
        std::cout << "Processing data: " << data << std::endl;
    }
};

データ解析アクター

データ解析アクターは、センサーアクターから送信されたデータを受信し、解析を行います。

class DataProcessorActor : public Actor {
public:
    void analyzeData(int data) {
        send([data]() {
            std::cout << "Analyzing data: " << data << std::endl;
            // データ解析の模擬処理
        });
    }
};

システムの動作

センサーアクターとデータ解析アクターを使用して、リアルタイムデータ処理システムを構築します。

int main() {
    SensorActor sensor;
    DataProcessorActor processor;

    sensor.startCollecting(processor);

    // 少し待ってから終了することでメッセージの処理が行われる
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

この例では、センサーアクターが定期的にデータを収集し、そのデータをデータ解析アクターに送信します。データ解析アクターは、受信したデータを解析します。この仕組みにより、センサーからのデータ収集と解析が並行して効率的に行われます。

リアルタイムシステムの利点

  • 低遅延: アクターパターンとタスクキューパターンを使用することで、タスク間の遅延を最小限に抑え、リアルタイム性を確保します。
  • 高信頼性: アクターの独立性により、一部のアクターが故障しても他のアクターに影響を与えないため、システムの信頼性が向上します。
  • スケーラビリティ: システムの負荷に応じて、アクターやワーカーの数を動的に調整することで、スケーラビリティを実現します。

このように、C++のマルチスレッドプログラミングとデザインパターンを組み合わせることで、リアルタイムデータ処理システムを効率的かつ効果的に構築することが可能です。この手法は、産業用制御システム、金融取引システム、ゲーム開発など、多岐にわたる分野で応用されています。

演習問題とその解説

本記事で学んだ内容を確認するために、いくつかの演習問題を用意しました。これらの問題を通して、C++のマルチスレッドプログラミングとデザインパターンについて理解を深めましょう。

演習問題1: 基本的なスレッドの生成と管理

以下のコードを完成させて、スレッドを生成し、それぞれのスレッドで異なるメッセージを表示するプログラムを作成してください。

#include <iostream>
#include <thread>

// スレッドで実行する関数
void threadFunction1() {
    std::cout << "スレッド1が実行されています" << std::endl;
}

void threadFunction2() {
    std::cout << "スレッド2が実行されています" << std::endl;
}

int main() {
    // スレッドの生成と実行
    std::thread myThread1(/* ここにコードを追加 */);
    std::thread myThread2(/* ここにコードを追加 */);

    // スレッドの終了を待機
    myThread1.join();
    myThread2.join();

    return 0;
}

解説

スレッドを生成する際には、std::threadコンストラクタに実行する関数を渡します。joinメソッドを使用して、メインスレッドが子スレッドの終了を待つようにします。

int main() {
    // スレッドの生成と実行
    std::thread myThread1(threadFunction1);
    std::thread myThread2(threadFunction2);

    // スレッドの終了を待機
    myThread1.join();
    myThread2.join();

    return 0;
}

演習問題2: スレッド間通信の実装

次のコードを完成させて、プロデューサースレッドが整数を生成し、コンシューマースレッドがその整数を処理するプログラムを作成してください。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queue<int> messageQueue;
std::mutex mtx;
std::condition_variable cv;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        messageQueue.push(i);
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !messageQueue.empty(); });
        int message = messageQueue.front();
        messageQueue.pop();
        std::cout << "Consumed: " << message << std::endl;
        if (message == 9) break; // 終了条件
    }
}

int main() {
    std::thread prodThread(producer);
    std::thread consThread(consumer);

    prodThread.join();
    consThread.join();

    return 0;
}

解説

このプログラムでは、std::queueを使ってスレッド間で整数メッセージを送受信します。std::mutexstd::condition_variableを使用して、スレッド間の同期を行います。

演習問題3: アクターパターンの実装

次のコードを完成させて、アクタークラスを使用してメッセージを処理するプログラムを作成してください。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>

class Actor {
public:
    using Task = std::function<void()>;

    Actor() : stop(false) {
        worker = std::thread([this]() { this->processMessages(); });
    }

    ~Actor() {
        {
            std::unique_lock<std::mutex> lock(mtx);
            stop = true;
        }
        taskQueue.push([]{});
        worker.join();
    }

    void send(Task task) {
        {
            std::unique_lock<std::mutex> lock(mtx);
            taskQueue.push(task);
        }
        cv.notify_one();
    }

private:
    void processMessages() {
        while (true) {
            Task task;
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this] { return !taskQueue.empty() || stop; });
                if (stop && taskQueue.empty()) break;
                task = taskQueue.front();
                taskQueue.pop();
            }
            task();
        }
    }

    std::queue<Task> taskQueue;
    std::mutex mtx;
    std::condition_variable cv;
    std::thread worker;
    bool stop;
};

class PrintActor : public Actor {
public:
    void printMessage(const std::string& message) {
        send([message]() {
            std::cout << "Message: " << message << std::endl;
        });
    }
};

int main() {
    PrintActor actor;
    actor.printMessage("Hello, Actor!");
    actor.printMessage("This is an example of Actor Pattern.");

    // 少し待ってから終了することでメッセージの処理が行われる
    std::this_thread::sleep_for(std::chrono::seconds(1));

    return 0;
}

解説

アクターパターンでは、各アクターが独立して動作し、メッセージを処理します。sendメソッドを使用してメッセージをキューに追加し、processMessagesメソッドでメッセージを処理します。

これらの演習を通じて、C++のマルチスレッドプログラミングとデザインパターンの理解を深めることができます。各演習を実装し、動作を確認してみてください。

まとめ

本記事では、C++におけるマルチスレッドプログラミングとデザインパターンについて詳しく解説しました。マルチスレッドの基本概念から始まり、C++でのスレッド生成と管理、スレッド間通信の手法、そしてアクターパターンとタスクキューパターンの具体的な実装方法を学びました。さらに、これらのデザインパターンを組み合わせることで、リアルタイムシステムや大規模な並行処理システムを効率的に構築する方法を紹介しました。

各セクションで取り上げた具体的なコード例や演習問題を通じて、実際のプログラムに適用するための知識とスキルを身につけることができたと思います。マルチスレッドプログラミングは複雑ですが、適切なデザインパターンを用いることで、効率的かつ信頼性の高いシステムを構築することが可能です。

これからのプロジェクトにおいて、今回学んだ技術を活用し、より高度な並行処理を実現してください。

コメント

コメントする

目次