多線程之旅:避免死鎖——簡單的鎖分級(鎖排序)


死鎖是很討厭的(雖然活鎖更討厭),如何避免死鎖呢?

在兩個線程間的循環等待是比較容易識別的,但是在死鎖的形成中如果包含多個線程,那么就是難以發現的(現實中不少這種情況)。

首先來看看死鎖形成的幾個必要條件

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;
        }
    }

 

 鎖分級的復雜性:

如果要為鎖指定級別,那么就需要規划並遵守一定的原則。我們很難從一開始就提出很完備的鎖分級策略。也比較難估計哪些地方使用到鎖。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM