首頁>科技>

敵不動 我不動!

小明同學,上週未和家人出去遊玩去了。剛學了鎖的用法,小明終於完善的模擬出了早餐店的流水線,所以他遊玩很開心。但是回家的路上,卻遇到了煩心事!

由於天氣很好,小明一家人遊玩到了天黑才驅車回家。正值交通擁堵的時候,在他們即將行進到一個環島的時候,交通完全堵死了。

傳說中的堵死

小明在車上看著道路資源被無限的佔用著,聯想到多執行緒程式設計中的鎖:要是限制一下進入環島的車輛的數量,是不是就不會出現這種無限的堵死在狀態呢!

由於車輛太多,已經進入環島的車輛,出環島的路被堵死,無法出去,無法釋放佔用的道路資源。想要進入環島的車輛,卻又因為無法進入環島而又一直佔用著環島的出口。這不就是個死迴圈嘛!

在堵了一個小時之後,小明一家人終於走過了這個環島,不一會就到家了。

死鎖演示

聰明的小明,直覺告訴他,在多執行緒中,也可能會出現這種現象。於是他用兩個鎖物件,模擬了環島堵死的場景:

class Program{    static long uniqueRes = 0; // 唯一資源    // 鎖物件    static object roadOut = new object();    static object roadIn = new object();    static void EnterCircle()    {        lock (roadIn)        {            // 模擬堵在了入口            for (int i = 0; i < 10000; i++)            {                uniqueRes += 1;            }            lock (roadOut)            {                // 模擬堵在了出口                for (int i = 0; i < 10000; i++)                {                    uniqueRes -= 1;                }            }        }    }    static void ExitCircle()    {        // 將資源鎖上        lock (roadOut)        {            // 模擬堵在了出口處            while (true)            {                uniqueRes -= 1;            }        }    }    static void Main(string[] args)    {        Console.WriteLine("Hello Thread Dead Locker!");        var t1 = new Thread(EnterCircle);        var t2 = new Thread(ExitCircle);        t1.Start();        t2.Start();        t1.Join();        t2.Join();        Console.WriteLine($"sum: {uniqueRes}");        Console.ReadKey();    }}

執行結果如下:

程式無法結束

果然程式也堵住了。

其實小明模擬的情形,是一種資源的惡意佔用。一直佔用資源而不歸還,導致其他執行緒無法訪問資源。這種情況,在實際程式設計中,不多見,除非是惡意程式碼。一般正常的程式碼,就算長時間佔用資源,最終都會歸還資源的。

但是有另外一種交叉佔用資源導致的死鎖問題,要特別小心。我把小明的程式碼做了一點修改:

class Program{    static long uniqueResA = 0; // 唯一資源      static long uniqueResB = 0; // 唯一資源    // 鎖物件    static object A = new object();    static object B = new object();    static void Fun1()    {        lock (A)        {            for (int i = 0; i < 10000; i++)            {                uniqueResA += 1;            }            lock (B)            {                for (int i = 0; i < 10000; i++)                {                    uniqueResB -= 1;                }            }        }    }    static void Fun2()    {        lock (B)        {            for (int i = 0; i < 10000; i++)            {                uniqueResB -= 1;            }            lock (A)            {                for (int i = 0; i < 10000; i++)                {                    uniqueResA += 1;                }            }        }    }    static void Main(string[] args)    {        Console.WriteLine("Hello Thread Dead Locker!");        var t1 = new Thread(Fun1);        var t2 = new Thread(Fun2);        t1.Start();        t2.Start();        t1.Join();        t2.Join();        Console.WriteLine($"A: {uniqueResA}  B: {uniqueResB}");        Console.ReadKey();    }}

執行3次的結果如下:

資源交叉佔用可能導致死鎖

由於Fun1和Fun2幾乎同時執行,剛開始Fun1請求資源A成功,Fun2請求資源B也成功。但是當Fun1開始請求資源B的時候,如果Fun2還沒有釋放B,就會導致Fun1阻塞。Fun1阻塞又導致其無法歸還資源A,進而導致Fun2又無法請求到資源A,進而又導致Fun2阻塞。。。

你不讓我也沒法讓

小明看到這個結果,就舉手表示有疑問:為什麼第三次執行成功了呢?

我並沒有回答小明的問題,而是佈置了作業:

1.同學們回家之後,將Fun1和Fun2中的迴圈中的計算的次數調高和調低,然後觀察程式多次執行出現死鎖的機率。

2.如何避免出現死鎖現象?

3.如果出現死鎖,如何解除死鎖?

小明似乎明白了什麼,但是我立即示意他不要說出來!先驗證再說結論!

聰明的小明

附:參考答案

1.迴圈中,計算次數越多,出現死鎖的機率越高

2.a 儘量避免長時間佔用資源

2.b 避免資源交叉請求

3.a 有序資源分配法

3.b 銀行家演算法

踩坑記錄

這次發現一個和此前我的理解不同的地方,分享給大家:

請問開啟4個執行緒,同時執行如下的DealRes函式,會導致死鎖嗎?

static long uniqueRes = 0; // 唯一資源// 鎖物件static object locker = new object();static void DealRes(){    // 將資源鎖上    lock (locker)    {        // 再來一次lock,會死鎖嗎?!        lock (locker)        {            for (int i = 0; i < 10000; i++)            {                uniqueRes += 1;            }        }    }}
下期預告

下面是給同學們準備的乾貨,正在陸續發貨中哦:

Task

Parallel

await/async

Linq與PLinq (ParallelEnumerable)

。。。 。。。

21
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 華為AR路由器與Cisco路由器建立IPSec隧道