ジョブ ベースのスレッド アーキテクチャを実装します。これは一方で、メインスレッドがジョブを追加できるキューがあることを意味します。一方、使用可能な CPU コアの数に応じて、これらのジョブを消費してキューから削除するワーカー スレッドがあります。
さて、C++ でそれを実装するには 2 つの方法が思い浮かびます。最初のものはテンプレートベースです。Task
1 つのジョブを表すテンプレートがあります。ラムダの可能性がある関数を保持し、データへのアクセスを提供します。
これを使用するには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 つのアプローチのパフォーマンスと柔軟性の違いは何ですか?