【问题标题】:"If two threads are using Pulse and Wait to interact, this could result in a deadlock."“如果两个线程正在使用 Pulse 和 Wait 进行交互,这可能会导致死锁。”
【发布时间】:2015-05-06 15:17:39
【问题描述】:

基本上 load() 用于生产者(只有一个调度程序线程加载 _tickQueue),而 Unload 用于消费者(只有一个专用线程执行该函数)。 _tickQueue 是一个受锁保护的常规队列(我使用它自己作为 lock() 的参数)。令人惊讶的是,它导致了僵局。

    public void Load(Tick tick)
    {
        lock (_tickQueue)
        {
            while (_tickQueue.Count >= CapSize)
            {
                Monitor.Wait(_tickQueue);
            }

            _tickQueue.Enqueue(tick);
            if (!_receivedTickCounts.ContainsKey(tick.Underlier))
            {
                _receivedTickCounts.Add(tick.Underlier, 0);
            }
            Console.WriteLine("Received {1} ticks for {0}", tick.Underlier, ++_receivedTickCounts[tick.Underlier]);
            Monitor.Pulse(_tickQueue);
        }
    }

    private void Unload()
    {
        while (true)
        {
            try
            {
                Tick tick;
                lock (_tickQueue)
                {
                    while (_tickQueue.Count == 0)
                    {
                        Monitor.Wait(_tickQueue);
                    }

                    tick = _tickQueue.Dequeue();
                    Monitor.Pulse(_tickQueue);
                }

                Persist(tick);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

标题中的评论在这里找到: https://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse%28v=vs.110%29.aspx

我对“重要”段落的理解是:监视器类不维护状态(以 ResetEvent 的方式)意味着死锁。给出了一个具体的例子:当两个线程使用Pulse和Wait交互时,如果一个线程在另一个线程不在等待队列中时发出脉冲,那么就会发生死锁。

有人可以具体(例如,给出发生死锁的场景)指出我在程序中做错了什么吗?我没有看到任何可能导致死锁的情况。 谢谢。

===================编辑=====================

具体来说,我很想知道为什么监视器的以下编码模式突然不起作用 - 一定与 .net 中的监视器实现有关?

lock
    while(wait condition is met)
    {
        wait()
    }

    // critical section: doing work

    signal();// or broadcast()
unlock

【问题讨论】:

  • 为什么不使用BlockingCollection<T>msdn.microsoft.com/en-us/library/dd267312(v=vs.110).aspx
  • @SimonSvensson 嗨,西蒙。感谢您的评论。我确信有上百万种方法可以完成任务。想听听为什么我的方法不起作用。让我们将其视为一项技术练习,通过它我可以了解有关线程的更多信息。
  • 请为您的问题找到一个更有意义的标题!

标签: c# .net multithreading deadlock monitor


【解决方案1】:

我怀疑您对这两种方法都施加了无休止的等待。您正在使用 While 循环不断检查条件来包围您的 Monitor 方法调用。对于 CapSize 和 _tickQueue.Count 的某些值,您的 Load() 和 Unload() 方法都将永远等待。这里不明显的是 CapSize 的值,它是恒定的还是变化的? _tickQueue 是线程安全的吗?

如果我们在 tick = _tickQueue.Dequeue(); 上遇到错误怎么办?在 Unload() 中,_tickQueue.Count 达到 0,Load() 方法是 Waiting() 吗? Load() 将永远等待。

我会避免让您的消费者方法 Pulse 来通知生产者方法它已准备好进行更多工作。您的消费者应该只在没有更多工作要做时等待(队列为空)。您的 Producer 将更适合控制自己的工作计划,并在新工作排队时通知消费者。为什么不将 Producer 放在 Timer 上?

最后,我相信提供的代码只是提供了太多的故障点。我可以建议一个替代实现吗?这使用了线程安全的 ConcurrentQueue 集合并消除了讨论的问题。

public class StackOverflowMonitorExample
{

    ConcurrentQueue<Tick> _tickQueue = new ConcurrentQueue<Tick>();
    object locker = new object();
    bool stopCondition = false;

    public void Load(Tick tick)
    {
        _tickQueue.Enqueue(tick);

        lock (locker)
        {
            Monitor.Pulse(locker);
        }

    }

    private void Unload()
    {
        while (!stopCondition)
        {
            try
            {
                Tick nextWorkItem = null;

                _tickQueue.TryDequeue(out nextWorkItem);

                if (nextWorkItem != null)
                {
                    Persist(nextWorkItem);
                }
                else
                {
                    lock (locker)
                    {
                        Monitor.Wait(locker);
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }
}

这消除了大的锁定部分,并消除了消费者和生产者之间的大部分信号。 Producer 只会将新项目添加到队列中,并且 Pulse() 会通知有新工作可用。只要项目仍在队列中,并且未满足停止条件,消费者就会循环并继续工作。如果队列计数达到 0,则消费者将等待新的队列条目。

【讨论】:

    猜你喜欢
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    • 2012-10-18
    • 2021-10-11
    • 2013-04-28
    • 2016-06-05
    相关资源
    最近更新 更多