「スレッドプールの詳しい説明と応用」

導入

スレッド プールは、コンピュータのマルチコア プロセッサを効果的に利用してプログラムのパフォーマンスと効率を向上させるために使用されるマルチスレッド テクノロジです。スレッド プールは、複数のスレッドを管理およびスケジュールし、実行のためにアイドル状態のスレッドにタスクを割り当てることができるため、スレッドの作成と破棄を繰り返すオーバーヘッドを回避し、スレッドが多すぎることによるシステム リソースの浪費を回避します。
スレッドプールは、スレッドプールマネージャー、ワーカースレッド、タスクキューで構成され、マネージャーの制御により複数のタスクがワーカースレッドに割り当てられて処理されることで、プログラムのパフォーマンスと効率が向上します。
CPP スレッド プールは、マルチスレッド プログラミングを実現するための高度なテクノロジであり、同時実行性の高い環境でのプログラムの実行効率を向上させることができます。

考慮する

すべてのタスクが無限に実行されると、スレッド プール内のスレッド数が制限されるため、タスクが時間内に処理されなくなり、プログラムの応答速度とパフォーマンスが低下します。
この場合、スレッド プール内のスレッドの数を増やしてプログラムの同時処理能力を高めることを検討できます。さらに、無限に実行されるタスクに対して、スケジュールされた中断やバッチ実行などの特別な処理手段を採用して、タスクがスレッド リソースを長時間占有することを回避できます。
スレッド プール内のスレッドの数を増やすとプログラムの同時処理能力は向上しますが、システム リソースもある程度消費されることに注意してください。したがって、スレッド プール サイズを設定するときは、システムの利用可能なリソースと、最適なパフォーマンスと効率を達成するための実際のビジネス ニーズを包括的に考慮する必要があります。

完全なコードの詳細

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

class ThreadPool {
    
    
public:
    ThreadPool(size_t threadCount) : m_stop(false) {
    
    
        for (size_t i = 0; i < threadCount; ++i) {
    
    
            m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
                while(true) {
    
    
                    std::unique_lock<std::mutex> lock(m_mutex);
                    m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                    if (m_stop && m_tasks.empty()) return;`在这里插入代码片`

                    std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                    m_tasks.pop();
                    lock.unlock();
                    
                    task(); // 执行任务
                }
            });
        }
    }
    
    ~ThreadPool() {
    
    
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_stop = true;
        }
        m_cv.notify_all();

        for (auto& thread : m_threads) {
    
    
            thread.join();
        }
    }

    template<class F, class... Args>
    void addTask(F&& f, Args&&... args) {
    
    
        std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_tasks.emplace(std::move(task));
        }
        m_cv.notify_one();
    }
    private:
    std::vector<std::thread> m_threads;
    std::queue<std::function<void()>> m_tasks;
    std::mutex m_mutex;
    std::condition_variable m_cv;
    bool m_stop;
    }

このコードは、次の特性を持つ単純なスレッド プールを実装します:
● スレッド プール内のスレッドの数はコンストラクター パラメーターで指定されます;
● タスクの追加をサポートし、タスクは呼び出し可能なオブジェクトです;
● スレッド プールはすべてのタスクが完了するまで待機します。破棄されたときに実行される すべてのスレッドを完了して停止します;
● スレッド プールは、複数のスレッドが addTask メソッドを同時に呼び出すことをサポートし、スレッド プールはミューテックスと条件変数を使用してスレッドの同期を実現します。
使用例:

void foo(int n) {
    
    
    std::cout << "Task " << n << " is running in thread " << std::this_thread::get_id() << std::endl;
}

int main() {
    
    
    ThreadPool pool(4);
    for (int i = 0; i < 10; ++i) {
    
    
        pool.addTask(foo, i);
    }
    return 0;
}

この例では、スレッド プールを作成し、10 個のタスクを追加します。各タスクはテキストを出力します。異なるタスクが実行のために異なるスレッドに割り当てられていることがわかります。

詳しく説明してください

スレッド プールは、一般的に使用されるマルチスレッド プログラミング モデルであり、複数のタスクを固定数のスレッドに割り当てて実行することで、頻繁なスレッドの作成と破棄によるオーバーヘッドを回避し、プログラムのパフォーマンスと効率を大幅に向上させることもできます。

スレッドプールコンストラクター

コンストラクターでは、スレッド プール内のスレッドの数を指定する必要があります。C++11 では可変個引数テンプレートを使用して、さまざまな種類の呼び出し可能オブジェクトをサポートします。コンストラクターでは、スレッド配列を初期化し、ラムダ式を使用して各スレッドの実行関数を作成します。ラムダ式の while ループは常にタスク キュー内でタスクが実行されるのを待機し、スレッド プールが終了したかどうかのシグナルも待機します。

ThreadPool(size_t threadCount) : m_stop(false) {
    
    
    for (size_t i = 0; i < threadCount; ++i) {
    
    
        m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
            while(true) {
    
    
                std::unique_lock<std::mutex> lock(m_mutex);
                m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                if (m_stop && m_tasks.empty()) return;

                std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                m_tasks.pop();
                lock.unlock();
                
                task(); // 执行任务
            }
        });
    }
}

スレッドプールデストラクタ

デストラクターでは、join() メソッドを呼び出して、すべてのスレッドが終了してリソースを解放するのを待つ必要があります。同時に、タスクキューが空のときにスレッドプールの実行関数を終了できるように、m_stop 信号を true に設定する必要もあります。

~ThreadPool() {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_stop = true;
    }
    m_cv.notify_all();

    for (auto& thread : m_threads) {
    
    
        thread.join();
    }
}

タスク機能の追加

addTask()関数は、スレッドプールにタスクを追加する関数です。可変個引数テンプレートを使用して、さまざまな種類の呼び出し可能オブジェクトをサポートします。まず呼び出し可能オブジェクトを function<void()> タイプのタスクに変換し、次にそのタスクをタスク キューに追加し、待機中のスレッドにタスクを実行するよう通知する必要があります。

1template<class F, class... Args>
2void addTask(F&& f, Args&&... args) {
    
    
3    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
4    {
    
    
5        std::unique_lock<std::mutex> lock(m_mutex);
6        m_tasks.emplace(std::move(task));
7    }
8    m_cv.notify_one();
9}

タスクキューと同期メカニズム

タスク キューと同期メカニズムは、スレッド プール実装の中核です。タスク キューでは、 std::queue<std::function<void()>> タイプを使用してタスクを格納します。ここで、 std::function<void()> は呼び出し可能なオブジェクトのタイプを表します。複数のスレッドがタスク キューを共有するため、スレッドの同期を実現するには、ミューテックス (m_mutex) と条件変数 (m_cv) を使用する必要があります。
1std::queue<std::function<void()>> m_tasks;
2std::mutex m_mutex;
3std::condition_variable m_cv;
実行関数では、条件変数 m_cv の wait() メソッドを使用して、タスクキュー 実行するタスクがあります。新しいタスクが追加されるか、スレッド プールが終了すると、notify_one() メソッドを使用して待機中のスレッドを起動します。同時に、タスクを実行するときは、複数のスレッドが同じタスクを同時に実行しないように、まずタスク キューからタスクを削除する必要があります。

std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
if (m_stop && m_tasks.empty()) return;

呼び出し可能なオブジェクトのラッパー

タスクを追加するときは、さまざまなタイプの呼び出し可能オブジェクトを function<void()> タイプのタスクに変換できるように、呼び出し可能オブジェクトをラップする必要があります。ここでは、 std::bind() メソッドを使用して呼び出し可能オブジェクトのバインディングを実装し、 std::forward(f) および std::forward(args)... を使用してパラメータを転送します。

template<class F, class... Args>
void addTask(F&& f, Args&&... args) {
    
    
    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_tasks.emplace(std::move(task));
    }
    m_cv.notify_one();
}

要約する

上記のコードは単純なスレッド プールを実装しています。これは、開発者がコンピュータのマルチコア CPU を有効に活用して、プログラムのパフォーマンスと効率を向上させるのに役立ちます。スレッド プールの実装ではスレッドの同期に注意する必要がありますが、std::mutex と std::condition_variable を使用することで簡単にスレッドの同期を実現できます。同時に、リソース リークを避けるために、デストラクターですべてのタスクが完了するまで待機するように注意してください。

おすすめ

転載: blog.csdn.net/yiyu20180729/article/details/130730565