• 多线程之旅:避免死锁——简单的锁分级(锁排序)


    死锁是很讨厌的(虽然活锁更讨厌),如何避免死锁呢?

    在两个线程间的循环等待是比较容易识别的,但是在死锁的形成中如果包含多个线程,那么就是难以发现的(现实中不少这种情况)。

    首先来看看死锁形成的几个必要条件

    1、互斥

    2、等待

    3、不可抢占

    4、循环等待

     除了完全避免多线程编程之外,如果要避免死锁,那么必须要使得上面这4个条件中有任意一个不满足。

    1、互斥是大多数锁的一种固有性质,你没办法改变它。

    2、如果程序持有的锁不会多于一个,那么就不会发生死锁问题。但是这通常也是不可能的。

    3、不可抢占,线程中断和线程终止并非是实现这个功能的合适方式。

    4、循环等待。通过在锁上施加一种顺序,并且要求线程按照这种顺序来获取锁。那么就不会发生循环的获取锁操作,也就不会发生死锁。这也是这四种条件中最容易消除的。

    我们用一个简单的例子实现简单的锁分级。

    第一种会有并发问题的写法,我们本意是通过形参fromAccount , toAccount 来表明锁的顺序,可这是做不到的。因此如果我们调换了一下传入的实参的顺序,就会产生死锁问题。

        class BankAccount 
        {
            public int id { get; set; }
            public decimal Balance { get; set; }
    
            public static void Transfer(BankAccount fromAccount, BankAccount toAccount, decimal amount)
            {
                lock (fromAccount)
                {
                    if (fromAccount.Balance < amount)
                    {
                        throw new Exception("Insufficient funds");
                    }
    
                    lock (toAccount)
                    {
                        fromAccount.Balance -= amount;
                        toAccount.Balance += amount;
                    }
                }
            }
        }

     
    我们可以通过比较id来保证顺序,同时这种写法不会引入新的开销。但是这种写法局限性比较大,不通用。

    class BankAccount
        {
            public int id { get; set; }
            public decimal Balance { get; set; }
    
            public static void SafeTransfer(BankAccount fromAccount, BankAccount toAccount, decimal amount)
            {
                if (fromAccount.id < toAccount.id)
                {
                    lock (fromAccount)
                    {
                        lock (toAccount)
                        {
                            transferMoney(fromAccount, toAccount, amount);
                        }
                    }
                }
                else if (fromAccount.id > toAccount.id)
                {
                    lock (toAccount)
                    {
                        lock (fromAccount)
                        {
                            TransferMoney(fromAccount, toAccount, amount);
                        }
                    }
                }
            }
    
    
            private static void TransferMoney(BankAccount fromAccount, BankAccount toAccount, decimal amount)
            {
                if (fromAccount.Balance < amount)
                {
                    throw new Exception("Insufficient funds");
                }
    
                fromAccount.Balance -= amount;
                toAccount.Balance += amount;
            }
    
        }

     本质上是通过固定一种顺序,因此我们想到可以通过排序的方式使得可以接受多个锁,并且更通用。

        class MultiLocksHelper<T> where T :IComparable<T>
        {
            internal static void Enter(params T[] locks)
            {
                Array.Sort(locks);
    
                int i = 0;
                try
                {
                    for (; i < locks.Length; i++)
                    {
                        Monitor.Enter(locks[i]);
                    }
                }
                catch
                {
                    for(int j= i-1;j>0;j--)
                    {
                        Monitor.Exit(locks[j]);
                    }
                    throw;
                }
            }
    
            internal static void Exit(params T[] locks)
            {
                Array.Sort(locks);
    
                for (int i = locks.Length - 1; i >= 0; i--)
                {
                    Monitor.Exit(locks[i]);
                }
            }
        }

    这时调用起来就很简单了,只有几行代码。

    class BankAccount : IComparable<BankAccount>
        {
            public int id { get; set; }
            public decimal Balance { get; set; }
    
            public static void GenericSafeTransfer(BankAccount fromAccount, BankAccount toAccount, decimal amount)
            {
                MultiLocksHelper<BankAccount>.Enter(fromAccount, toAccount);
    
                try
                {
                    TransferMoney(fromAccount, toAccount, amount);
                }
                finally
                {
                    MultiLocksHelper<BankAccount>.Exit(fromAccount, toAccount);
                }
    
            }
    
    
            private static void TransferMoney(BankAccount fromAccount, BankAccount toAccount, decimal amount)
            {
                if (fromAccount.Balance < amount)
                {
                    throw new Exception("Insufficient funds");
                }
    
                fromAccount.Balance -= amount;
                toAccount.Balance += amount;
            }
    
    
            public int CompareTo(BankAccount other)
            {
                if (this.id > other.id)
                    return 1;
                else
                    return -1;
            }
        }

     锁分级的复杂性:

    如果要为锁指定级别,那么就需要规划并遵守一定的原则。我们很难从一开始就提出很完备的锁分级策略。也比较难估计哪些地方使用到锁。

  • 相关阅读:
    FZU 2098 刻苦的小芳(卡特兰数,动态规划)
    卡特兰数总结
    FZU 1064 教授的测试(卡特兰数,递归)
    HDU 4745 Two Rabbits(区间DP,最长非连续回文子串)
    Java 第十一届 蓝桥杯 省模拟赛 正整数的摆动序列
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
  • 原文地址:https://www.cnblogs.com/lwzz/p/3100053.html
Copyright © 2020-2023  润新知