2013-05-06 11 views
8

ManualResetEvent経由でクロスプロセスイベントを作成しました。このイベントが発生すると、n個の異なるプロセスのn個のスレッドがブロックされ、新しいデータをフェッチするための実行が開始されます。問題は、ManualResetEvent.Setの直後に即時のリセットがあると、待機中のすべてのスレッドが起動することはないようです。ドキュメントはクロスプロセスイベント - すべてのウェイターを確実に解放します。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspxマニュアルリセットイベントオブジェクトの状態が通知され

は、それが明示的にResetEvent 機能によって非シグナルにリセットされるまで が合図のままそこにかなり漠然としています。オブジェクトの状態が通知されている間に、 が指定されたイベントオブジェクトの待機操作を開始する任意の数の待機スレッドまたはスレッドを解放できます。

PulseEventと呼ばれる方法がありますが、私が必要としているのとまったく同じように見えますが、残念ながらそれにも欠陥があります。

同期オブジェクト上で待機しているスレッドが一瞬 カーネルモードAPCで待機状態から除去され、APCが完了した後、その後、待機状態を に戻すことができます。スレッド 状態からスレッドが削除されている間にPulseEvent の呼び出しが発生した場合、PulseEventは が呼び出された時点で待機しているスレッドのみを解放するため、スレッドは解放されません。 したがって、PulseEventは信頼性が低く、新しい アプリケーションでは使用しないでください。代わりに、条件変数を使用します。

ここで、条件変数を使用することをおすすめします。

条件変数は、スレッド が特定の条件が発生するまで待つことを可能にする同期プリミティブです。条件変数は ユーザーモードオブジェクトです。はプロセス間で共有できません

ドキュメントに続いて、私はそれを確実に実行する運が不足しているようです。 1つのManualResetEventで明記されている制限なしに同じことを達成する簡単な方法はありますか、各リスナープロセスに対してレスポンスイベントを作成して、各サブスクライブされた呼び出し元に対してACKを取得する必要がありますか?その場合、サブスクライブされたプロセスのpidを登録するには小さな共有メモリが必要ですが、それはそれ自体の問題のセットをもたらすようです。 1つのプロセスがクラッシュしたり応答しなかったりするとどうなりますか? ....

いくつかの文脈を与える。他のすべてのプロセスが共有メモリの場所から読み込むべき新しい状態を公開しました。一度に複数の更新が発生した場合でも1つの更新を見逃しても問題ありませんが、プロセスは少なくとも最新の最新の値を読み取る必要があります。私はタイムアウトでポーリングすることができましたが、それは正しい解決策ではないようです。

現在、私は、.NET 4.0以来ダウン

ChangeEvent = new EventWaitHandle(false, EventResetMode.ManualReset, counterName + "_Event"); 

ChangeEvent.Set(); 
Thread.Sleep(1); // increase odds to release all waiters 
ChangeEvent.Reset(); 

答えて

4

生産者がすべての消費者を目覚めさせなければならないケースを処理するための1つの汎用オプションであり、消費者の数が進化しているのは、移動フェンスアプローチを使用することです。このオプションは共有メモリIPC領域も必要です。この方法は、多くのプロセスがスケジューリングと負荷を必要とする場合、特に絶望的に過負荷状態になっているマシンを除いて常に起動しなければならない場合に、仕事が存在しないときに消費者を目覚めさせることがあります。

手動リセットイベントをいくつか作成し、プロデューサに設定される次のイベントのカウンタを維持させます。すべてのイベントは、NextToFireイベントを除き、設定されたままになります。コンシューマプロセスは、NextToFireイベントを待機します。プロデューサがすべての消費者を目覚めさせたい場合、次+ 1イベントをリセットして現在のイベントを設定します。すべてのコンシューマーは最終的にスケジュールされ、新しいNextToFireイベントを待ちます。この効果は、プロデューサだけがResetEventを使用することですが、コンシューマは、いつ起きるのかを常に知っています。

すべてのユーザーの初期化:(擬似コードは、C/C++ではなく、C#の場合)

// Create Shared Memory and initialise NextToFire; 
pSharedMemory = MapMySharedMemory(); 
if (First to create memory) pSharedMemory->NextToFire = 0; 

HANDLE Array[4]; 
Array[0] = CreateEvent(NULL, 1, 0, "Event1"); 
Array[1] = CreateEvent(NULL, 1, 0, "Event2"); 
Array[2] = CreateEvent(NULL, 1, 0, "Event3"); 
Array[3] = CreateEvent(NULL, 1, 0, "Event4"); 

プロデューサーがウェイクするために、すべての

long CurrentNdx = pSharedMemory->NextToFire; 
long NextNdx = (CurrentNdx+1) & 3; 

// Reset next event so consumers block 
ResetEvent(Array[NextNdx]); 

// Flag to consumers new value 
long Actual = InterlockedIncrement(&pSharedMemory->NextToFire) & 3; 

// Next line needed if multiple producers active. 
// Not a perfect solution 
if (Actual != NextNdx) ResetEvent(Actual); 

// Now wake them all up 
SetEvent(CurrentNdx); 

消費者はロジック

long CurrentNdx = (pSharedMemory->NextToFire) & 3; 
WaitForSingleObject(Array[CurrentNdx], Timeout); 
+0

これは私が思い付くことができるすべての競争条件を解決する非常に良い解決策です。ありがとう! –

2

にしています、あなたは、プロセスのメモリを同期するメモリマップトファイルを使用することができます。この場合、MemoryMappedFileにカウンタを書き込み、それをワーカープロセスからデクリメントします。カウンタがゼロに等しい場合、メインプロセスはイベントをリセットすることができます。ここにサンプルコードがあります。

メイン処理

//number of WorkerProcess 
int numWorkerProcess = 5; 

//Create MemroyMappedFile object and accessor. 4 means int size. 
MemoryMappedFile mmf = MemoryMappedFile.CreateNew("test_mmf", 4); 
MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(); 

EventWaitHandle ChangeEvent = new EventWaitHandle(false, EventResetMode.ManualReset, counterName + "_Event"); 

//write counter to MemoryMappedFile 
accessor.Write(0, numWorkerProcess); 

//..... 

ChangeEvent.Set(); 

//spin wait until all workerProcesses decreament counter 
SpinWait.SpinUntil(() => { 

    int numLeft = accessor.ReadInt32(0); 
    return (numLeft == 0); 
}); 


ChangeEvent.Reset(); 

WorkerProcess

//Create existed MemoryMappedfile object which created by main process. 
MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("test_mmf"); 
MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(); 

//This mutex object is used for decreament counter. 
Mutex mutex = new Mutex(false, "test_mutex"); 
EventWaitHandle ChangeEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "start_Event"); 

//.... 

ChangeEvent.WaitOne(); 

//some job... 

//decrement counter with mutex lock. 
mutex.WaitOne(); 
int count = accessor.ReadInt32(0); 
--count; 
accessor.Write(0, count); 
mutex.ReleaseMutex(); 
///////////////////////////////////// 

環境が.NET 4.0未満の場合は、あなたがのWin32 APIからCreateFileMapping関数の機能を使用することによって実現することができます。

+0

これは機能しますが、非常に壊れやすいです。プロセスが強制終了されたり、ハングアップしたりすると、グローバルカウンタが減少することはなく、永久に待機します。タイムアウトを使用すると、あきらめた直後にプロセスがカウンタを減らすことができます(起こります)。これは、「フル」カウンタを間違って減少させることになります。登録されたプロセスがサイレントに終了した場合、サブスクライバの数はどのように決定されますか?その場合、推測に基づいて加入者数を調整する必要があります。このソリューションでは競合状態が多すぎます。 –

+0

@Alois Krausはい、あなたは正しいです。それは労働者のプロセスにおける事故について考慮していない。しかし、あなたのシステムがなぜワーカープロセスの数を決定できないのか分かりません。ほとんどの場合、ワーカープロセスを作成して管理するコントローラプロセスが存在するためです。したがって、これらのコントローラプロセスは、ワーカープロセスの数と各条件(エラーを含む)を知る必要があります。いずれにせよ、最良の解決策は、各ワーカープロセスのイベントを準備し、コントローラプロセスと通信することです。 – Darksanta

+0

私は加入者数がわからず、制御装置もないので、それほど簡単ではありません。たとえ加入者がいつでも契約を解除できるので、それが間違っていることがわかっても。これは、古いサブスクリプションカウントに基づいてイベントを配信している間にサブスクライバがサブスクライブを解除する別の競合条件です。 –

0

を待つあなたが書きました: "私が必要とするものを正確に行うと思われるPulseEventでも、残念ながらそれにも欠陥がある"。これはPulseEventに欠陥があることは事実ですが、私は手動リセットイベントに欠陥があることに同意できません。それは非常に信頼できる。手動リセットイベントを使用できるケースがあり、使用できない場合があります。それはすべてのためのワン・フィットです。自動リセットイベントやパイプなどのツールがたくさんあります。

スレッドに通知する最も良い方法は、定期的に通知する必要があるが、プロセス間でデータを送信する必要はありません。自動リセットイベント。スレッドごとに独自のイベントが必要です。だから、スレッドと同じくらい多くのイベントがあります。

プロセスにデータを送信する必要がある場合は、名前付きパイプを使用することをお勧めします。自動リセットイベントとは異なり、プロセスごとに独自のパイプは必要ありません。各名前付きパイプには、サーバーと1つ以上のクライアントがあります。多数のクライアントが存在する場合、同じ名前付きパイプの多くのインスタンスが、各クライアントのオペレーティングシステムによって自動的に作成されます。名前付きパイプのすべてのインスタンスは同じパイプ名を共有しますが、各インスタンスには独自のバッファとハンドルがあり、クライアント/サーバー通信のための別のコンジットを提供します。インスタンスを使用すると、複数のパイプ・クライアントが同じ名前付きパイプを同時に使用できます。どのプロセスも、あるパイプのサーバーと別のパイプのクライアントの両方として機能することができ、その逆も可能であり、ピアツーピア通信を可能にします。

名前付きパイプを使用する場合は、シナリオ内でイベントが発生する必要はなく、プロセスで何が起きてもデータの配信が保証されます。プロセスごとに長い遅延が発生する可能性がありますあなたの特別な関与なしに、データはできるだけ早くすぐに配達されます。

すべてのスレッド(プロセス)の1つのイベントは、通知が1回だけであればOKです。この場合、自動リセットイベントではなく手動リセットイベントが必要です。たとえば、アプリケーションがすぐに終了することを通知する必要がある場合、この共通の手動リセットイベントを通知することがあります。しかし、前に書いたように、あなたのシナリオでは、名前付きパイプが最善の選択です。

関連する問題