2

我正在阅读 MSDN 上的 AutoResetEvent 文档,并且以下警告让我很困扰..

“重要:不能保证每次调用Set方法都会释放一个线程。如果两个调用靠得太近,以至于第二个调用发生在一个线程被释放之前,那么只有一个线程被释放。就好像“

但是这个警告基本上扼杀了拥有这种线程同步技术的理由。例如,我有一个可以容纳工作的列表。而且只有一个生产者会将工作添加到列表中。我有消费者(不止一个),等待从列表中获得工作.. 像这样..

制片人:

void AddJob(Job j)
{
    lock(qLock)
    {
        jobQ.Enqueue(j);
    }

    newJobEvent.Set(); // newJobEvent is AutoResetEvent
}

消费者

void Run()
{
    while(canRun)
    {
        newJobEvent.WaitOne();

        IJob job = null;

        lock(qLock)
        {
            job = jobQ.Dequeue();
        }

        // process job
    }
}

如果上述警告属实,那么如果我很快将两个作业排入队列,那么只有一个线程会接手该作业,不是吗?我假设 Set 将是原子的,即它执行以下操作:

  1. 设置事件
  2. 如果线程正在等待,则选择一个线程唤醒
  3. 重置事件
  4. 运行选定的线程。

所以我基本上对 MSDN 中的警告感到困惑。这是一个有效的警告吗?

4

2 回答 2

2

即使警告不是真的并且 Set 是原子的,你为什么要在这里使用 AutoResetEvent 呢?假设您有一些生产者连续排队 3 个事件,并且有一个消费者。处理完第二个作业后,消费者阻塞并且从不处理第三个作业。

我会使用ReaderWriterLockSlim进行这种类型的同步。基本上,您需要多个生产者才能拥有写锁,但您不希望消费者在仅读取队列大小时长时间锁定生产者。

于 2012-02-29T05:21:33.083 回答
2

MSDN 上的消息确实是有效消息。内部发生的事情是这样的:

  1. 线程 A 等待事件
  2. 线程 B 设置事件
  3. [如果线程 A 处于自旋锁中]
    1. [是] 线程 a 检测到事件已设置,取消设置并恢复其工作
    2. [no] 事件会告诉线程 A 唤醒,一旦唤醒,线程 A 将取消设置事件恢复其工作。

请注意,内部逻辑不是同步的,因为线程 B 不会等待线程 A 继续其业务。您可以通过引入一个临时的 ManualResetEvent 来实现同步,一旦线程 A 继续其工作并且线程 B 必须等待,它必须发出信号。由于 Windows 线程模型的内部工作,默认情况下不会这样做。我猜该文档具有误导性,但正确地说 Set 方法仅释放一个或多个等待线程。

或者,我建议您查看 .NET 4.0 中引入的 BCL 的 System.Collections.Concurrent 命名空间中的 BlockingCollection 类,它完全符合您的要求

于 2012-02-29T05:22:16.613 回答