web-dev-qa-db-ja.com

C ++ 11のスレッドプーリング

関連する質問

C++ 11について:

Boostについて:


スレッドのプールからタスクをに送信し、それらを何度も作成および削除しますか?これは、参加せずに再同期する永続的なスレッドを意味します。


次のようなコードがあります。

namespace {
  std::vector<std::thread> workers;

  int total = 4;
  int arr[4] = {0};

  void each_thread_does(int i) {
    arr[i] += 2;
  }
}

int main(int argc, char *argv[]) {
  for (int i = 0; i < 8; ++i) { // for 8 iterations,
    for (int j = 0; j < 4; ++j) {
      workers.Push_back(std::thread(each_thread_does, j));
    }
    for (std::thread &t: workers) {
      if (t.joinable()) {
        t.join();
      }
    }
    arr[4] = std::min_element(arr, arr+4);
  }
  return 0;
}

反復ごとにスレッドを作成して結合するのではなく、反復ごとにワーカースレッドにタスクを送信し、一度だけ作成することを好みます。

104
Yktula

C++スレッドプールライブラリ、 https://github.com/vit-vit/ctpl を使用できます。

その後、作成したコードを次のコードに置き換えることができます

#include <ctpl.h>  // or <ctpl_stl.h> if ou do not have Boost library

int main (int argc, char *argv[]) {
    ctpl::thread_pool p(2 /* two threads in the pool */);
    int arr[4] = {0};
    std::vector<std::future<void>> results(4);
    for (int i = 0; i < 8; ++i) { // for 8 iterations,
        for (int j = 0; j < 4; ++j) {
            results[j] = p.Push([&arr, j](int){ arr[j] +=2; });
        }
        for (int j = 0; j < 4; ++j) {
            results[j].get();
        }
        arr[4] = std::min_element(arr, arr + 4);
    }
}

必要な数のスレッドを取得し、反復で何度も作成したり削除したりしません。

70
vit-vit

スレッドのプールとは、すべてのスレッドが常に実行されていることを意味します。つまり、スレッド関数は決して戻りません。スレッドに意味のある何かを与えるには、スレッドに何かすべきことを伝えるためだけでなく、実際の作業データを伝えるために、スレッド間通信のシステムを設計する必要があります。

通常、これにはある種の同時データ構造が含まれ、各スレッドはおそらく何らかの種類の条件変数でスリープし、実行する作業があるときに通知されます。通知を受信すると、1つまたは複数のスレッドが起動し、並行データ構造からタスクを回復し、処理し、同様の方法で結果を保存します。

その後、スレッドはさらに実行すべき作業があるかどうかをチェックし、そうでない場合はスリープ状態に戻ります。

結局のところ、普遍的に適用できる「作業」の自然な概念がないため、これをすべて自分で設計する必要があります。それはかなりの作業であり、あなたが正しくしなければならないいくつかの微妙な問題があります。 (バックグラウンドでスレッド管理を行うシステムが必要な場合は、Goでプログラミングできます。)

57
Kerrek SB

これは私の回答から非常によく似た別の投稿にコピーされています。

1)システムがサポートできるスレッドの最大数から開始します。

int Num_Threads =  thread::hardware_concurrency();

2)効率的なスレッドプールの実装のために、Num_Threadsに従ってスレッドが作成されたら、新しいスレッドを作成したり、古いスレッドを破棄したり(結合する)ことをお勧めします。パフォーマンスが低下し、アプリケーションがシリアルバージョンより遅くなる場合もあります。

各C++ 11スレッドは、関数内で無限ループで実行され、新しいタスクが取得されて実行されるのを常に待機している必要があります。

このような関数をスレッドプールにアタッチする方法は次のとおりです。

int Num_Threads = thread::hardware_concurrency();
vector<thread> Pool;
for(int ii = 0; ii < Num_Threads; ii++)
{  Pool.Push_back(thread(Infinite_loop_function));}

3)Infinite_loop_function

これは、タスクキューを待機する「while(true)」ループです。

void The_Pool:: Infinite_loop_function()
{
    while(true)
    {
        {
            unique_lock<mutex> lock(Queue_Mutex);

            condition.wait(lock, []{return !Queue.empty()});
            Job = Queue.front();
            Queue.pop();
        }
        Job(); // function<void()> type
    }
};

4)ジョブをキューに追加する関数を作成します

void The_Pool:: Add_Job(function<void()> New_Job)
{
    {
        unique_lock<mutex> lock(Queue_Mutex);
        Queue.Push(New_Job);
    }
    condition.notify_one();
}

5)キューに任意の関数をバインドします

Pool_Obj.Add_Job(std::bind(&Some_Class::Some_Method, &Some_object));

これらの要素を統合すると、独自の動的スレッドプールができます。これらのスレッドは常に実行され、ジョブの実行を待機します。

構文エラーがある場合は謝罪し、これらのコードを入力しましたが、メモリが不足しています。申し訳ありませんが、完全なスレッドプールコードを提供することはできません。これは私の仕事の整合性に違反します。

52
PhD AP EcE

スレッドプールは、イベントループとして機能する関数にすべてバインドされた一連のスレッドです。これらのスレッドは、タスクの実行、または独自の終了を無限に待ちます。

スレッドプールジョブは、ジョブを送信するためのインターフェイスを提供し、これらのジョブの実行ポリシー(スケジュールルール、スレッドのインスタンス化、プールのサイズ)を定義(およびおそらく変更)し、スレッドおよび関連リソースのステータスを監視します。

そのため、汎用プールの場合、タスクの内容、起動方法、中断方法、結果(その質問の約束と未来の概念を参照)、スレッドが応答するイベントの種類を定義することから始めなければなりませんに、どのようにそれらを処理するか、これらのイベントをタスクによって処理されるイベントからどのように区別するか。ご覧のとおり、これは非常に複雑になり、ソリューションがますます複雑になるにつれて、スレッドの動作方法に制限が課されます。

イベントを処理するための現在のツールは、かなりベアボーン(*)です:ミューテックスのようなプリミティブ、条件変数、およびその上にあるいくつかの抽象化(ロック、バリア)。しかし、場合によっては、これらのアブストラクトは不適切であることが判明することがあり(関連する question を参照)、プリミティブの使用に戻る必要があります。

他の問題も管理する必要があります。

  • 信号
  • i/O
  • ハードウェア(プロセッサアフィニティ、異種セットアップ)

これらはあなたの設定でどのように機能しますか?

この回答は、ブーストとstlを目的とした既存の実装を指します。

非常に粗雑な実装 別の質問のためのスレッドプールを提供しましたが、これは上記で概説した多くの問題に対処していません。あなたはそれを積み上げたいかもしれません。また、他の言語の既存のフレームワークを見て、インスピレーションを見つけたいかもしれません。


(*)まったく逆に、私はそれを問題とは思わない。 Cから継承されたC++の精神そのものだと思います。

18
didierc

このような何かが役立つかもしれません(動作中のアプリから取得)。

#include <memory>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

struct thread_pool {
  typedef std::unique_ptr<boost::asio::io_service::work> asio_worker;

  thread_pool(int threads) :service(), service_worker(new asio_worker::element_type(service)) {
    for (int i = 0; i < threads; ++i) {
      auto worker = [this] { return service.run(); };
      grp.add_thread(new boost::thread(worker));
    }
  }

  template<class F>
  void enqueue(F f) {
    service.post(f);
  }

  ~thread_pool() {
    service_worker.reset();
    grp.join_all();
    service.stop();
  }

private:
  boost::asio::io_service service;
  asio_worker service_worker;
  boost::thread_group grp;
};

次のように使用できます。

thread_pool pool(2);

pool.enqueue([] {
  std::cout << "Hello from Task 1\n";
});

pool.enqueue([] {
  std::cout << "Hello from Task 2\n";
});

効率的非同期キューイングメカニズムの再発明は簡単ではないことに注意してください。

Boost :: asio :: io_serviceは非常に効率的な実装であるか、実際にはプラットフォーム固有のラッパーのコレクションです(たとえば、WindowsでI/O完了ポートをラップします)。

3
rustyx

編集:これには、C++ 17と概念が必要になりました。 (9/12/16時点で、g ++ 6.0+のみで十分です。)

ただし、テンプレートの推論はより正確であるため、新しいコンパイラを取得する価値があります。明示的なテンプレート引数を必要とする関数はまだ見つかりません。

また、適切な呼び出し可能オブジェクト(を使用し、静的に型保証されています!!!)。

また、同じAPIを使用するオプションの緑色のスレッド化優先度スレッドプールも含まれています。ただし、このクラスはPOSIXのみです。ユーザー空間のタスク切り替えにucontext_t APIを使用します。


このために簡単なライブラリを作成しました。以下に使用例を示します。 (自分で書く必要があると判断する前に見つけたものの1つだったので、私はこれに答えています。)

bool is_prime(int n){
  // Determine if n is prime.
}

int main(){
  thread_pool pool(8); // 8 threads

  list<future<bool>> results;
  for(int n = 2;n < 10000;n++){
    // Submit a job to the pool.
    results.emplace_back(pool.async(is_prime, n));
  }

  int n = 2;
  for(auto i = results.begin();i != results.end();i++, n++){
    // i is an iterator pointing to a future representing the result of is_prime(n)
    cout << n << " ";
    bool prime = i->get(); // Wait for the task is_prime(n) to finish and get the result.
    if(prime)
      cout << "is prime";
    else
      cout << "is not prime";
    cout << endl;
  }  
}

asyncは、任意の(またはvoid)戻り値と任意の(または引数なし)引数を持つ関数を渡すことができ、対応するstd::futureを返します。結果を取得する(またはタスクが完了するまで待つ)には、将来get()を呼び出します。

Githubは次のとおりです。 https://github.com/Tyler-Hardin/thread_pool

2
Tyler

これは、非常にシンプルで理解しやすく使いやすい別のスレッドプール実装であり、C++ 11標準ライブラリのみを使用し、用途に合わせて確認または変更できます。プール:

https://github.com/progschj/ThreadPool

2
Halcyon
Follwoing [PhD EcE](https://stackoverflow.com/users/3818417/phd-ece) suggestion, I implemented the thread pool:

function_pool.h

#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cassert>

class Function_pool
{

private:
    std::queue<std::function<void()>> m_function_queue;
    std::mutex m_lock;
    std::condition_variable m_data_condition;
    std::atomic<bool> m_accept_functions;

public:

    Function_pool();
    ~Function_pool();
    void Push(std::function<void()> func);
    void done();
    void infinite_loop_func();
};

function_pool.cpp

#include "function_pool.h"

Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition(), m_accept_functions(true)
{
}

Function_pool::~Function_pool()
{
}

void Function_pool::Push(std::function<void()> func)
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_function_queue.Push(func);
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    lock.unlock();
    m_data_condition.notify_one();
}

void Function_pool::done()
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_accept_functions = false;
    lock.unlock();
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    m_data_condition.notify_all();
    //notify all waiting threads.
}

void Function_pool::infinite_loop_func()
{
    std::function<void()> func;
    while (true)
    {
        {
            std::unique_lock<std::mutex> lock(m_lock);
            m_data_condition.wait(lock, [this]() {return !m_function_queue.empty() || !m_accept_functions; });
            if (!m_accept_functions && m_function_queue.empty())
            {
                //lock will be release automatically.
                //finish the thread loop and let it join in the main thread.
                return;
            }
            func = m_function_queue.front();
            m_function_queue.pop();
            //release the lock
        }
        func();
    }
}

main.cpp

#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>

Function_pool func_pool;

class quit_worker_exception : public std::exception {};

void example_function()
{
    std::cout << "bla" << std::endl;
}

int main()
{
    std::cout << "stating operation" << std::endl;
    int num_threads = std::thread::hardware_concurrency();
    std::cout << "number of threads = " << num_threads << std::endl;
    std::vector<std::thread> thread_pool;
    for (int i = 0; i < num_threads; i++)
    {
        thread_pool.Push_back(std::thread(&Function_pool::infinite_loop_func, &func_pool));
    }

    //here we should send our functions
    for (int i = 0; i < 50; i++)
    {
        func_pool.Push(example_function);
    }
    func_pool.done();
    for (unsigned int i = 0; i < thread_pool.size(); i++)
    {
        thread_pool.at(i).join();
    }
}
2
pio

STLの外部に依存関係のないスレッドプールは完全に可能です。私は最近、まったく同じ問題に対処するために小さな header-only threadpool library を作成しました。動的なプールのサイズ変更(実行時のワーカー数の変更)、待機、停止、一時停止、再開などをサポートします。それがあなたのお役に立てば幸いです。

0
cantordust

Boostライブラリの thread_pool を使用できます。

void my_task(){...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    boost::asio::thread_pool pool(threadNumbers);

    // Submit a function to the pool.
    boost::asio::post(pool, my_task);

    // Submit a lambda object to the pool.
    boost::asio::post(pool, []() {
      ...
    });
}

また、オープンソースコミュニティの threadpool を使用できます。

void first_task() {...}    
void second_task() {...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    pool tp(threadNumbers);

    // Add some tasks to the pool.
    tp.schedule(&first_task);
    tp.schedule(&second_task);
}
0
Amir Forsati