【问题标题】:How to prevent deadlocks in the following C# code?如何防止以下 C# 代码中的死锁?
【发布时间】:2009-01-14 18:46:55
【问题描述】:

以下 C# 类用于多线程环境。我删除了很多实际代码。几乎同时调用 MethodA 和 MethodB 时会出现问题。 IsDepleted 属性中的锁顺序并不能解决问题。从 IsDepleted 属性中删除 lock(WaitingQueue) 可以解决死锁问题,但是当另一个线程在 WaitingQueue.Count == 0 和 Processing.Count == 0 语句之间从 WaitingQueue 添加/删除项目时,此解决方案会导致问题。

using System.Collections.Generic;

class Example
{
    bool IsDepleted
    {
        get
        {
            lock (Processing)
            {
                lock (WaitingQueue)
        {
                    return WaitingQueue.Count == 0
             && Processing.Count == 0;
        }
            }
        }
    }

    private readonly List<object> Processing = new List<object>();
    private readonly Queue<object> WaitingQueue = new Queue<object>();

    public void MethodA(object item)
    {
        lock (WaitingQueue)
        {
            if (WaitingQueue.Count > 0)
            {
                if (StartItem(WaitingQueue.Peek()))
                {
                    WaitingQueue.Dequeue();
                }
            }
        }
    }

    public void MethodB(object identifier)
    {
        lock (Processing)
        {
            Processing.Remove(identifier);
            if (!IsDepleted)
            {
                return;
            }
        }
    //Do something...
    }

    bool StartItem(object item)
    {
        //Do something and return a value
    }
}

【问题讨论】:

    标签: c# .net multithreading deadlock


    【解决方案1】:

    这取决于您想要快速修复还是严格修复。

    快速解决方法是在所有情况下都使用一个锁对象。

    例如private readonly object _lock = new object();

    然后锁定它。但是,根据您的具体情况,这可能对性能的影响超出您的接受程度。

    即你的代码会变成这样:

    using System.Collections.Generic;
    
    class Example
    {
        private readonly object _lock = new object();
    
        bool IsDepleted
        {
            get
            {
                lock (_lock)
                {
                    return WaitingQueue.Count == 0
                     && Processing.Count == 0;
                }
            }
        }
    
        private readonly List<object> Processing = new List<object>();
        private readonly Queue<object> WaitingQueue = new Queue<object>();
    
        public void MethodA(object item)
        {
            lock (_lock)
            {
                if (WaitingQueue.Count > 0)
                {
                    if (StartItem(WaitingQueue.Peek()))
                    {
                        WaitingQueue.Dequeue();
                    }
                }
            }
        }
    
        public void MethodB(object identifier)
        {
            lock (_lock)
            {
                Processing.Remove(identifier);
                if (!IsDepleted)
                {
                    return;
                }
            }
            //Do something...
        }
    
        bool StartItem(object item)
        {
            //Do something and return a value
        }
    }
    

    【讨论】:

    • 非快速修复会是什么?
    • 对整个程序的锁使用情况进行详细分析,选择更合适的锁方案。
    【解决方案2】:

    获取方法 A 中的 Processing 锁和方法 B 中的 WaitingQueue 锁(换句话说,让它看起来像第一个代码块)。这样一来,您始终以相同的顺序获取锁,并且您永远不会死锁。

    【讨论】:

    • 在这种情况下,不需要两个锁。 :)
    • 如果你要这样做,那么你总是做一个双锁,你还不如把它简化为一个锁
    • 以相同的顺序锁定互斥锁是防止死锁的绝对必要条件。所需的锁顺序确实不意味着一个人总是只需要一把锁。如果操作正确,使用多个互斥锁可能会提高性能。
    • @gimpf 不是基于提供的示例...我想如果在代码库的其他地方单独使用锁可能会产生影响,但如果没有,那么这只是不必要的开销。
    【解决方案3】:

    简化您的代码并仅使用一个对象来锁定。您也可以将锁替换为:

    Monitor.TryEnter(Processing,1000)

    这会给你 1 秒的超时时间。所以本质上:

            if (Monitor.TryEnter(Processing, 1000))
            {
                try
                {
                    //do x
                }
                finally
                {
                    Monitor.Exit(Processing);
                }
            }
    

    现在你不会停止死锁,但你可以处理没有锁的情况。

    【讨论】:

    • 防止死锁的最佳方法是完全避免锁定资源。如果您需要保护这样的关键区域......监视器方法是一个很好的解决方案。不错!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多