Cross Process Event - Release all waiters reliably
我已经通过ManualResetEvent创建了一个跨进程事件。当确实发生此事件时,应取消阻止n个不同进程中的n个线程,并开始运行以获取新数据。问题在于,ManualResetEvent.Set紧随其后的Reset似乎不会导致所有等待的线程唤醒。那里的文档很模糊
http://msdn.microsoft.com/zh-cn/library/windows/desktop/ms682396(v = vs.85).aspx
When the state of a manual-reset event object is signaled, it remains
signaled until it is explicitly reset to nonsignaled by the ResetEvent
function. Any number of waiting threads, or threads that subsequently
begin wait operations for the specified event object, can be released
while the object's state is signaled.
有一个名为PulseEvent的方法似乎可以完全满足我的需要,但不幸的是,它也存在缺陷。
A thread waiting on a synchronization object can be momentarily
removed from the wait state by a kernel-mode APC, and then returned to
the wait state after the APC is complete. If the call to PulseEvent
occurs during the time when the thread has been removed from the wait
state, the thread will not be released because PulseEvent releases
only those threads that are waiting at the moment it is called.
Therefore, PulseEvent is unreliable and should not be used by new
applications. Instead, use condition variables.
现在,MS建议不要使用条件变量。
Condition variables are synchronization primitives that enable threads
to wait until a particular condition occurs. Condition variables are
user-mode objects that cannot be shared across processes.
以下文档似乎使我可靠地用完了运气。有没有一种简单的方法就可以通过一个ManualResetEvent在没有规定的限制的情况下完成同一件事,还是我需要为每个侦听器进程创建一个响应事件以获取每个订阅的呼叫者的ACK?在那种情况下,我将需要一个小的共享内存来注册所订阅进程的pid,但这似乎带来了自己的一系列问题。当一个进程崩溃或不响应时会发生什么? ....
提供一些背景信息。我有一个新状态要发布,即所有其他进程应从共享内存位置读取。当一次发生多个更新时可以错过一个更新,但是该过程必须至少读取最新的值。我可以轮询超时,但这似乎不是正确的解决方案。
目前我要
1 2 3 4 5 | ChangeEvent = new EventWaitHandle(false, EventResetMode.ManualReset, counterName +"_Event"); ChangeEvent.Set(); Thread.Sleep(1); // increase odds to release all waiters ChangeEvent.Reset(); |
处理生产者必须唤醒所有消费者并且消费者数量不断变化的情况的一种通用选择是使用移动栅栏方法。此选项也需要共享内存IPC区域。该方法有时会导致在没有任何工作的情况下唤醒使用者,特别是在许多进程需要调度且负载很高的情况下,但是除非在无望的过载机器上,否则它们总是会唤醒。
创建多个手动重置事件,并让生产者维护将要设置的下一个事件的计数器。除NextToFire事件外,所有事件均保持不变。使用者进程等待NextToFire事件。当生产者希望唤醒所有消费者时,它将重置Next 1事件并设置当前事件。最终将安排所有使用者,然后等待新的NextToFire事件。结果是只有生产者使用ResetEvent,而消费者始终知道哪个事件将唤醒它们。
所有用户初始化:(伪代码是C / C,而不是C#)
1 2 3 4 5 6 7 8 9 | // 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"); |
全部唤醒的制作人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); |
消费者等待逻辑
1 2 | long CurrentNdx = (pSharedMemory->NextToFire) & 3; WaitForSingleObject(Array[CurrentNdx], Timeout); |
从.NET 4.0开始,您可以使用MemoryMappedFile同步进程内存。在这种情况下,向MemoryMappedFile写入计数器,并将其从工作进程中递减。如果计数器等于零,则允许主进程重置事件。这是示例代码。
主要过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //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函数来实现。
您写道:" PulseEvent似乎完全可以满足我的需要,但不幸的是,它也有缺陷。"确实,PulseEvent存在缺陷,但是我不能同意手动重置事件存在缺陷。这是非常可靠的。在某些情况下,您可以使用手动重置事件,而在某些情况下,则无法使用它们。这不是万能的。还有很多其他工具,例如自动重置事件,管道等。
如果您需要定期通知线程,而又不需要跨进程发送数据,则通知线程的最佳方法是自动重置事件。您只需要每个线程自己的事件即可。因此,您拥有与线程一样多的事件。
如果您只需要将数据发送到流程,最好使用命名管道。与自动重置事件不同,您不需要每个进程都拥有自己的管道。每个命名管道都有一个服务器和一个或多个客户端。当有许多客户端时,操作系统将为每个客户端自动创建具有相同命名管道的许多实例。命名管道的所有实例共享相同的管道名称,但是每个实例都有其自己的缓冲区和句柄,并为客户端/服务器通信提供了单独的管道。实例的使用使多个管道客户端可以同时使用相同的命名管道。任何进程都可以同时充当一个管道的服务器和另一个管道的客户端,反之亦然,这使得对等通信成为可能。
如果您将使用命名管道,那么在您的方案中根本就不需要事件,并且无论流程发生什么情况,数据都将保证交付–每个流程可能会出现较长的延迟(例如, (通过交换)),但最终数据将尽快得到您的传递,而无需您的特殊参与。
如果通知仅一次,则所有线程(进程)的一个事件仅是确定的。在这种情况下,您将需要手动重置事件,而不是自动重置事件。例如,如果您需要通知您的应用程序将很快退出,则可以发出此常见的手动重置事件的信号。但是,正如我之前所写,在您的方案中,命名管道是最佳选择。