EventWaitHandle - WaitAny() 和 WaitOne() 的区别

14

我有三个线程,两个“工作者”和一个“管理者”。 “工作者”线程等待“管理者”线程发出信号后通过EventWaitHandle增加它们的计数器。这两个“工作者”线程之间唯一的区别就是其中一个使用EventWaitHandle.WaitAny(),而另一个则使用EventWaitHandle.WaitOne()

以下是代码:

class Program
{
    static void Main(string[] args)
    {
        MultiThreadedJobs multyThreadedJobs = new MultiThreadedJobs();
        multyThreadedJobs.Start();

        Console.ReadLine();

        multyThreadedJobs.Stop();
    }
}

class MultiThreadedJobs : IDisposable
{
    private EventWaitHandle syncEvent;
    private EventWaitHandle[] syncEventsArray;

    private Thread managerThread;
    private Thread firstWorkerThread;
    private Thread secondWorkerThread;

    private volatile bool running = false;


    public MultiThreadedJobs() // Ctor
    {
        syncEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "JobsSyncEvent");

        syncEventsArray = new EventWaitHandle[1];
        syncEventsArray[0] = syncEvent;

        managerThread = new Thread(ManagerThreadMethod);
        firstWorkerThread = new Thread(FirstWorkerThreadMethod);
        secondWorkerThread = new Thread(SecondWorkerThreadMethod);
    }

    public void Start()
    {
        running = true;

        managerThread.Start();
        firstWorkerThread.Start();
        secondWorkerThread.Start();
    }

    public void Stop()
    {
        running = false;
    }

    private void ManagerThreadMethod() // Manager Thread
    {
        while (running)
        {
            Thread.Sleep(1000);
            syncEvent.Set();
        }
    }

    private void FirstWorkerThreadMethod() // Worker Thread
    {
        int counter = 0;
        while (running)
        {
            syncEvent.WaitOne();
            counter++;
        }
    }

    private void SecondWorkerThreadMethod() // Worker Thread
    {
        int counter = 0;
        while (running)
        {
            EventWaitHandle.WaitAny(syncEventsArray);
            counter++;
        }
    }

    public void Dispose()
    {
        syncEvent.Close();
    }
}

问题是,使用 EventWaitHandle.WaitAny() 的第二个工作线程总是会捕获事件,并使第一个工作线程处于饥饿状态,而不是将事件分配给两个线程的比例约为50/50。


请展示所有相关的代码,您在哪里执行了 Thread.Start() - sll
@sircodesalot 谢谢,但我没有看到任何关于我的问题的答案。 - Jacob
  1. 这个类是继承自ContextBoundObject还是被标记为Synchronization]属性?
  2. 你在某处改变了running的值吗?
- sll
@sll,您可以查看所有代码。我已经编辑并将其添加到问题中。 - Jacob
@HansPassant,这段代码只是一个例子,真正的问题在于只有“EventWaitHandle.WaitAny()”被激活了。在现实世界中,我有理由以这种方式进行同步。 - Jacob
显示剩余4条评论
2个回答

13
您正在寻找软件工程中一个非常普遍的问题的解决方案,即生产者-消费者问题。链接的维基百科文章提供了良好的背景信息,特别是展示了如何以错误的方式解决它。
您肯定正在追求一种错误的解决方案。 AutoResetEvent过于简单。您已经发现了其中一个问题,它不提供公平性。它还会遇到很多其他问题,特别是当生产者线程比消费者线程更快地生成作业时,它会遭受严重的线程竞争。
示例代码过于人造,无法提供良好的替代方案。可以通过ReaderWriterLock/Slim类来实现低级别锁定。一个特别适合解决生产者/消费者问题的类是.NET 4 BlockingCollection类。支持任意数量的生产者和消费者线程,并提供节流以确保程序在消费者跟不上生产者时不会崩溃。您可以通过使用从生产者传递给消费者线程的假“令牌”来重新编写示例。一个BlockingColletion<bool>就能胜任。

非常感谢您详细的回答。在我的情况下,我需要同步“进程”而不仅仅是“线程”,为此我正在使用“命名事件”。但我发现这种机制并不公平,就像您所说的那样。 我的问题更加技术性。为什么EventWaitHandle不能提供公平性?是否有任何方法可以强制它更加公平? - Jacob

6
The WaitHandle类使客户端能够进行异步调用并等待:单个XML Web服务(WaitHandle.WaitOne),多个XML Web服务中的第一个(WaitHandle.WaitAny)或所有XML Web服务(WaitHandle.WaitAll)返回结果。如果您想在结果到达时处理结果,可以使用WaitHandle.WaitAny方法。该方法将表示操作已完成并标识已完成的操作。
这两种方法都是可重写的。根据传递的参数不同,实现也会有所变化。例如,WaitHandle.WaitAny方法(WaitHandle[], Int32, Boolean)等待指定数组中的任一元素接收信号,使用32位有符号整数来测量时间间隔,并指定是否在等待之前退出同步域。
WaitHandle.WaitOne方法(Int32, Boolean)当在派生类中重写时,阻塞当前线程直到当前WaitHandle接收到信号,使用32位有符号整数来测量时间间隔,并指定是否在等待之前退出同步域。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接