我认为来自ProducerConsumerQueue
的竞争条件
http://www.albahari.com/threading/part2.aspx#_Signaling_with_Event_Wait_Handles。这是代码:
using System;
using System.Threading;
using System.Collections.Generic;
class ProducerConsumerQueue : IDisposable
{
EventWaitHandle _wh = new AutoResetEvent (false);
Thread _worker;
readonly object _locker = new object();
Queue<string> _tasks = new Queue<string>();
public ProducerConsumerQueue()
{
_worker = new Thread (Work);
_worker.Start();
}
public void EnqueueTask (string task)
{
lock (_locker) _tasks.Enqueue (task);
_wh.Set();
}
public void Dispose()
{
EnqueueTask (null); // Signal the consumer to exit.
_worker.Join(); // Wait for the consumer's thread to finish.
_wh.Close(); // Release any OS resources.
}
void Work()
{
while (true)
{
string task = null;
lock (_locker)
if (_tasks.Count > 0)
{
task = _tasks.Dequeue();
if (task == null) return;
}
if (task != null)
{
Console.WriteLine ("Performing task: " + task);
Thread.Sleep (1000); // simulate work...
}
else
_wh.WaitOne(); // No more tasks - wait for a signal
}
}
}
考虑以下执行,其中C是消费者线程,P是生产者线程,t1,t2,t3是执行时间:
t1:由于队列为空,C不会进入任务执行
lock (_locker)
if (_tasks.Count > 0)
t2: P调用EnqueueItem(action)
t3: C到达_wh.WaitOne();
并永远等待(假设生产者停止添加新值)
答案 0 :(得分:2)
Every call to EnqueueItem
does two things - it first ensures that there is at least one item in the queue (P1), and then it calls Set
on the AutoResetEvent
(P2).
The consumer performs three activities in a loop - it tries to dequeue an item (C1), it then either processes the item (C2a) or it waits for the AutoResetEvent
to become set (C2b).
P1 and C1 are the items protected by the lock and therefore we know that one of these will occur before the other, and they will not be interleaved.
For C1 to conclude that no items are in the list, P1 must occur after it. But, since we know that P2 follows, we know that the AutoResetEvent
will definitely become set by P2 at some future point in time, and so the C2b wait will always be satisfied.