要件は次のとおりです。処理するアイテムはグローバル キューに格納されます。いくつかのハンドラ スレッドは、グローバル キューからアイテムを取得して処理します。プロデューサー スレッドはアイテムを継続的かつ迅速にグローバル キューに追加します(すべてのディーラー スレッドの処理速度よりもはるかに高速です。また、ハンドラー スレッドは計算集約型です。最高のパフォーマンスは、CPU を完全に使用することです)。そのため、もう 1 つのcountKeeping スレッドを使用して、 BOTTOMからTOPまでのように、キューの長さを特定の範囲に維持します (メモリを使いすぎないようにするため)。
ManualResetEvent
「キューに追加可能」ステータスの変更に対処するために使用します。グローバルキューは
Queue<Object> mQueue = new Queue<Object>;
ManualResetEvent waitingKeeper = new ManualResetEvent(false);
ハンドラスレッドは
void Handle()
{
while(true)
{
Object item;
lock(mQueue)
{
if(mQueue.Count > 0)
item = mQueue.Dequeue();
}
// deal with item, compute-intensive
}
}
プロデューサ スレッドは AddToQueue() 関数を呼び出してアイテムを mQueue に追加します。
void AddToQueue(Object item)
{
waitingKeeper.WaitOne();
lock(mQueue)
{
mQueue.Enqueue(item);
}
}
countKeeping スレッドは主に次のようなものです
void KeepQueueingCount()
{
while(true)
{
// does not use 'lock(mQueue)'
// because I don't need that specific count of the queue
// I just need the queue does not cost too much memory
if(mQueue.Count < BOTTOM)
waitingKeeper.Set();
else if(mQueue.Count > TOP)
waitingKeeper.Reset();
Thread.Sleep(1000);
}
}
ここで問題が発生します。
BOTTOM と TOP を小さい数値に設定すると、BOTTOM = 20、TOP = 100 のように、クアッド コア CPU ではうまく動作しますが (CPU 使用率が高くなります)、シングル CPU ではうまく動作しません (CPU 使用率は比較的大きく変動します。 )。
BOTTOM と TOP をより大きな数値 (BOTTOM = 100、TOP = 300) に設定すると、シングル CPU ではうまく機能しますが、クアッド コア CPU ではうまく機能しません。
どちらの環境でも、どちらの条件でも、メモリはあまり使用されていません (せいぜい 50M 程度)。
論理的には、BOTTOM と TOP を大きくすると、(メモリがあまり使用されていない場合) パフォーマンスが向上します。これは、ハンドラー スレッドが処理する項目が増えるためです。しかし、事実はそうではないようです。
問題の原因を見つけるために、いくつかの方法を試しました。lock(mQueue)
そして、スレッドを維持するために使用すると、上記の2つのCPU条件で両方ともうまく機能することがわかりました。
新しいcountKeepingスレッドは主にこのようなものです
void KeepQueueingCount()
{
bool canAdd = false;
while(true)
{
lock(mQueue)
{
if(mQueue.Count < BOTTOM)
canAdd = true;
else if(mQueue.Count > TOP)
canAdd = false;
}
if(canAdd)
waitingKeeper.Set();
else
waitingKeeper.Reset();
// I also did some change here
// when the 'can add' status changed, will sleep longer
// if not 'can add' status not changed, will sleep lesser
// but this is not the main reason
Thread.Sleep(1000);
}
}
だから私の質問は
- countKeeping thread
lock
で使用しなかった場合、グローバル キューの範囲がさまざまな CPU 条件でパフォーマンスに影響するのはなぜですか (ここでは、パフォーマンスは主に CPU 使用率です)。 - countKeeping threadで使用する
lock
と、さまざまな条件でパフォーマンスが向上します。何がこれに実際に影響しますか?lock
- を使用する代わりに「追加可能」ステータスを変更するより良い方法はあります
ManualResetEvent
か? - 私の要件に合ったより良いモデルはありますか?または、プロデューサースレッドが継続的かつ迅速に動作 するときに、メモリがあまり使用されないようにするより良い方法はありますか?
---UPDATE---
Producer threadの主要部分は以下の通りです。STEPは、データベースからの各クエリのアイテム数です。クエリは、すべてのアイテムがクエリされるまで、順番に行われます。
void Produce()
{
while(true)
{
// query STEP items from database
itemList = QuerySTEPFromDB();
if(itemList.Count == 0)
// stop all handler thread
// here, will wait for handler threads handle all items in queue
// then, all handler threads exit
else
foreach(var item in itemList)
AddToQueue(item);
}
}