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


130

関連する質問

C ++ 11について:

ブーストについて:


タスクを何度も作成したり削除したりせずタスクを送信するスレッドのプールを取得するにはどうすればよいですか?これは、結合せずに再同期する永続的なスレッドを意味します。


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

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;
}

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


1
ここに関連する質問と私の答えがあります。
ディディエルク、2013

1
tbbの使用について考えました(これはIntelですが、フリーでオープンソースであり、まさに望みどおりの動作をします。単純に(再帰的に分割可能な)タスクを送信し、スレッドについては心配しません)?
Walter、

2
このFOSSプロジェクトは、スレッドプールライブラリを作成するための私の試みです。必要に応じてチェックしてください。-> code.google.com/p/threadpool11
Etherealone

tbbの使用の何が問題になっていますか?
Walter

回答:


84

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);
    }
}

必要な数のスレッドが取得され、反復でそれらが何度も作成および削除されることはありません。


11
これが答えになるはずです。シングルヘッダー、読みやすく、わかりやすく、簡潔で標準に準拠したC ++ 11ライブラリ。すごい仕事!
ジョナサンH

@ vit-vit関数の例を挙げていただけますか?クラスメンバー関数をどのようにプッシュしますかresults[j] = p.push([&arr, j](int){ arr[j] +=2; });
Hani Goc '22 / 10/22

1
@HaniGoc参照によってインスタンスをキャプチャするだけです。
ジョナサンH

@ vit-vit STLバージョンを改善するためのプルリクエストを送信しました。
ジョナサンH

@ vit-vit:そのライブラリのメンテナに質問やヒントを問い合わせることは困難です。
einpoklum 2016年

82

これは私の答えから別の非常に類似した投稿にコピーされます。

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() || terminate_pool});
            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));

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

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

編集:プールを終了するには、shutdown()メソッドを呼び出します。

XXXX::shutdown(){
{
    unique_lock<mutex> lock(threadpool_mutex);
    terminate_pool = true;} // use this flag in condition.wait

    condition.notify_all(); // wake up all threads.

    // Join all threads.
    for(std::thread &every_thread : thread_vector)
    {   every_thread.join();}

    thread_vector.clear();  
    stopped = true; // use this flag in destructor, if not set, call shutdown() 
}

thread(const thread&)= deleteの場合、どのようにvector <thread>を取得しますか?
Christopher Pisz

1
@ChristopherPisz std::vectorでは、その要素をコピー可能にする必要はありません。あなたは移動のみのタイプ(とベクトルを使用することができunique_ptrthreadfuture、など)。
Daniel Langr

上記の例では、プールをどのように停止しますか?condition.waitも変数stop_を探してチェックする必要がありますif (stop_ == true) { break;}か?
John

@ジョン、上記のシャットダウン方法をご覧ください。
PhD AP EcE

2
shutdown()では、これはthread_vector.clear();である必要があります。thread_vector.empty();の代わりに 正しい?
sudheerbb

63

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

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

その後、スレッドはさらに処理が必要かどうかを確認し、そうでない場合はスリープ状態に戻ります。

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


11
「これはすべて自分で設計する必要があります」<-それが私がやろうとしていることです。けれども、ゴルーチンは素晴らしいようです。
Yktula、2013

2
@Yktula:まあ、それは非常に重要な仕事です。あなたの投稿からあなたがどんな種類の仕事をしたいのかさえはっきりしていません、そしてそれはソリューションの根本的な根本です。GoをC ++で実装することもできますが、それは非常に具体的なことであり、半数の人が別の何かが欲しいと不満を言うでしょう。
Kerrek SB 2013

19

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

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

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

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

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

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

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

同様の質問に対するこの回答は、boostとstl向けの既存の実装を指しています。

私は別の質問のためにスレッドプールの非常に大まかな実装を提案しましたが、これは上記で概説した多くの問題に対処していません。あなたはそれに基づいて構築したいかもしれません。また、インスピレーションを得るために、他の言語の既存のフレームワークを確認することもできます。


(*)それとは全く逆に、私はそれを問題としては見ていません。Cから継承されたC ++の精神そのものです。


4
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
ありがとう!これは、並列スレッド操作の開始に本当に役立ちました。実装を少し変更したバージョンを使用してしまいました。
ロビーキャップ

3

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

#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完了ポートをラップします)。


2
C ++ 11ではそれだけのブーストが必要ですか?たとえば、std::thread十分ではないでしょうか?
einpoklum 2016年

stdforには同等のものはありませんboost::thread_group。インスタンスのboost::thread_groupコレクションですboost::thread。しかし、もちろん、それは交換することは非常に簡単だboost::thread_groupvectorstd::thread秒。
rustyx 2016年

3

編集:これには、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


1
見た目は素晴らしいですが、vit-vitのヘッダーと比較すると素晴らしいでしょう。
ジョナサンH

1
@ Sh3ljohn、一見すると、APIは基本的に同じであるようです。vit-vitは、boostのロックフリーキューを使用します。(しかし、私の目標は特にstd :: *だけでそれを行うことでした。ロックフリーキューを自分で実装できると思いますが、それは難しく、エラーが発生しやすいようです。)また、vit-vitには関連付けられた.cppがありません。何をしているのか分からない人のために使用する方が簡単です。(例:github.com/Tyler-Hardin/thread_pool/issues/1
タイラー

彼/彼女はまた、私が過去数時間forkしていたstlのみのソリューションを持っています。最初はそれが至る所に共有ポインターを持つあなたのものより複雑に見えましたが、これは実際にホットリサイズを適切に処理するために必要です。
ジョナサンH

@ Sh3ljohn、ああ、私は熱いリサイズに気づかなかった。それはすばらしい。意図したユースケースに実際には含まれていないため、心配する必要はありませんでした。(私が個人的にサイズを変更したい場合は考えられませんが、それは想像力の欠如が原因である可能性があります。)
Tyler

1
ユースケースの例:サーバーでRESTful APIを実行していて、サービスを完全にシャットダウンする必要なく、メンテナンス目的でリソース割り当てを一時的に減らす必要がある場合。
ジョナサンH


3

ブーストライブラリから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, []() {
      ...
    });
}

オープンソースコミュニティからスレッドプールを使用することもできます

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);
}

1

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


githubアカウントを削除した(またはリンクが間違っている)ようです。このコードを別の場所に移動しましたか?
rtpax

1
@rtpax私はリポジトリを移動しました-私はそれを反映するように回答を更新しました。
cantordust
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.