2

ジョブ ベースのスレッド アーキテクチャを実装します。これは一方で、メインスレッドがジョブを追加できるキューがあることを意味します。一方、使用可能な CPU コアの数に応じて、これらのジョブを消費してキューから削除するワーカー スレッドがあります。

さて、C++ でそれを実装するには 2 つの方法が思い浮かびます。最初のものはテンプレートベースです。Task1 つのジョブを表すテンプレートがあります。ラムダの可能性がある関数を保持し、データへのアクセスを提供します。

これを使用するにはWork、ラムダ式など、関数オブジェクトに何かを格納する必要があります。さらに、Dataポインターをデータ オブジェクトにポイントし、Emptyその後 false に設定する必要があります。もちろん、オブジェクトをジョブ キューにアタッチする必要があります。ジョブをフェッチするワーカー スレッドはロックAccessされ、メイン スレッドはロックをチェックして解放し、結果を処理することができます。

template <class T>
class Task
{
public:
    Task()
    {
        Empty.store(true);
        Data = nullptr;
    }
    std::mutex Access;
    std::atomic<bool> Empty;
    std::function<void(T*)> Work;
    T *Data;
};

2 番目のアプローチは、継承ベースです。空のフラグとミューテックスは、最初のアプローチと同じままです。しかし、work 関数はオーバーライドしたい実際のメソッドです。さらに、派生タスクは必要なメンバーを追加できるため、データ ポインターはもう必要ありません。

class Task
{
public:
    Task()
    {
        Empty.store(true);
        Data = nullptr;
    }
    std::mutex Access;
    std::atomic<bool> Empty;
    virtual void Work() = 0;
};

わかりやすくするために、メイン スレッド内からジョブを開始する方法の短い例を 2 つ示します。最初のものから始めましょう。

int number;

Task<int> *example = new Task<int>();
example.Data = &number;
example.Empty.store(false);
example.Run = [](int* number){
    *number = 42;
});

Queue.push_back(example);

そして2つ目のアプローチ。

class Example : public Task
{
public:
    Example(int *number)
    {
        this->number = number;
    }
    void Work()
    {
        *number = 42;
    }
    int number;
};

int number;

Example *example = new Example(&number);    
example.Empty.store(false);

Queue.push_back(example);

これら 2 つのアプローチのパフォーマンスと柔軟性の違いは何ですか?

4

2 に答える 2

2

最初の例では、まったく新しいクラスを定義しなくても、任意のスレッド関数を使用できます。ただし、主な問題は、ユーザー データがスレッド関数に渡されるようにメモリを割り当てる必要があることです。そのため、整数を受け取るだけのタスクであっても、それへのポインターを渡す必要があります。

ただし、2 番目の方法では、任意のサイズの任意の数のメンバーをタスクに追加でき、実際のTaskインスタンスへのプライベート アクセスも提供されます。これは、後で役立つ場合があります。Taskさらに、テンプレート化されていないため、インスタンスのリストを簡単に管理できます。

仮想関数は関数ポインターとして実装されているだけなので、パフォーマンスに関する限り、それらはほとんど同じです。

于 2013-08-07T14:06:35.317 に答える
1

継承アプローチは明らかに最も慣用的で効率的な方法です。基本クラスTaskはすべてのワークシェアリングやキューイングなどを実装しますが、ユーザーは純粋な仮想メンバーをオーバーライドするだけで済みますWork()。これにより、タスク分散 (キューイングなど) の実装を、タスクの実際の作業とは無関係に実装できます。

Task::Work()マルチスレッド アプリケーションのパフォーマンスに関して言えば、仮想テーブルのルックアップ (call ) はほとんど心配する必要がありません。実際の問題は、作業キューの競合状態とサブタスクの効率的な分散です... Intel の tbb ( http://threadingbuildingblocks.org/ ) も参照してください。

于 2013-08-07T14:09:24.640 に答える