<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          咋辦,死鎖了

          共 2929字,需瀏覽 6分鐘

           ·

          2021-04-04 08:36

          突然發(fā)現(xiàn)我的圖解系統(tǒng)缺了「死鎖」的內(nèi)容,這就來補下。

          在面試過程中,死鎖也是高頻的考點,因為如果線上環(huán)境真多發(fā)生了死鎖,那真的出大事了。

          這次,我們就來系統(tǒng)地聊聊死鎖的問題。

          • 死鎖的概念;

          • 模擬死鎖問題的產(chǎn)生;

          • 利用工具排查死鎖問題;

          • 避免死鎖問題的發(fā)生;


          死鎖的概念

          在多線程編程中,我們?yōu)榱朔乐苟嗑€程競爭共享資源而導致數(shù)據(jù)錯亂,都會在操作共享資源之前加上互斥鎖,只有成功獲得到鎖的線程,才能操作共享資源,獲取不到鎖的線程就只能等待,直到鎖被釋放。

          那么,當兩個線程為了保護兩個不同的共享資源而使用了兩個互斥鎖,那么這兩個互斥鎖應用不當?shù)臅r候,可能會造成兩個線程都在等待對方釋放鎖,在沒有外力的作用下,這些線程會一直相互等待,就沒辦法繼續(xù)運行,這種情況就是發(fā)生了死鎖

          舉個例子,小林拿了小美房間的鑰匙,而小林在自己的房間里,小美拿了小林房間的鑰匙,而小美也在自己的房間里。如果小林要從自己的房間里出去,必須拿到小美手中的鑰匙,但是小美要出去,又必須拿到小林手中的鑰匙,這就形成了死鎖。

          死鎖只有同時滿足以下四個條件才會發(fā)生:

          • 互斥條件;

          • 持有并等待條件;

          • 不可剝奪條件;

          • 環(huán)路等待條件;

          互斥條件

          互斥條件是指多個線程不能同時使用同一個資源

          比如下圖,如果線程 A 已經(jīng)持有的資源,不能再同時被線程 B 持有,如果線程 B 請求獲取線程 A 已經(jīng)占用的資源,那線程 B 只能等待,直到線程 A 釋放了資源。

          持有并等待條件

          持有并等待條件是指,當線程 A 已經(jīng)持有了資源 1,又想申請資源 2,而資源 2 已經(jīng)被線程 C 持有了,所以線程  A 就會處于等待狀態(tài),但是線程  A 在等待資源 2 的同時并不會釋放自己已經(jīng)持有的資源 1

          不可剝奪條件

          不可剝奪條件是指,當線程已經(jīng)持有了資源 ,在自己使用完之前不能被其他線程獲取,線程 B 如果也想使用此資源,則只能在線程 A 使用完并釋放后才能獲取。

          環(huán)路等待條件

          環(huán)路等待條件指都是,在死鎖發(fā)生的時候,兩個線程獲取資源的順序構成了環(huán)形鏈

          比如,線程 A 已經(jīng)持有資源 2,而想請求資源 1, 線程 B 已經(jīng)獲取了資源 1,而想請求資源 2,這就形成資源請求等待的環(huán)形圖。


          模擬死鎖問題的產(chǎn)生

          Talk is cheap. Show me the code.

          下面,我們用代碼來模擬死鎖問題的產(chǎn)生。

          首先,我們先創(chuàng)建 2 個線程,分別為線程 A 和 線程 B,然后有兩個互斥鎖,分別是 mutex_A 和 mutex_B,代碼如下:

          接下來,我們看下線程 A 函數(shù)做了什么。

          可以看到,線程 A 函數(shù)的過程:

          • 先獲取互斥鎖 A,然后睡眠 1 秒;

          • 再獲取互斥鎖 B,然后釋放互斥鎖 B;

          • 最后釋放互斥鎖 A;


          然后,我們看看線程 B。


          可以看到,線程 B 函數(shù)的過程:

          • 先獲取互斥鎖 B,然后睡眠 1 秒;

          • 再獲取互斥鎖 A,然后釋放互斥鎖 A;

          • 最后釋放互斥鎖 B;

          然后,我們運行這個程序,運行結果如下:

          可以看到線程 B 在等待互斥鎖 A 的釋放,線程 A 在等待互斥鎖 B 的釋放,雙方都在等待對方資源的釋放,很明顯,產(chǎn)生了死鎖問題。


          利用工具排查死鎖問題

          如果你想排查你的 Java 程序是否死鎖,則可以使用 jstack 工具,它是 jdk 自帶的線程堆棧分析工具。

          由于小林的死鎖代碼例子是 C 寫的,在 Linux 下,我們可以使用 pstack + gdb 工具來定位死鎖問題。

          pstack 命令可以顯示每個線程的棧跟蹤信息(函數(shù)調(diào)用過程),它的使用方式也很簡單,只需要 pstack <pid> 就可以了。

          那么,在定位死鎖問題時,我們可以多次執(zhí)行 pstack 命令查看線程的函數(shù)調(diào)用過程,多次對比結果,確認哪幾個線程一直沒有變化,且是因為在等待鎖,那么大概率是由于死鎖問題導致的。

          我用 pstack 輸出了我前面模擬死鎖問題的進程的所有線程的情況,我多次執(zhí)行命令后,其結果都一樣,如下:

          可以看到,Thread 2 和 Thread 3 一直阻塞獲取鎖(pthread_mutex_lock)的過程,而且 pstack 多次輸出信息都沒有變化,那么可能大概率發(fā)生了死鎖。

          但是,還不能夠確認這兩個線程是在互相等待對方的鎖的釋放,因為我們看不到它們是等在哪個鎖對象,于是我們可以使用 gdb 工具進一步確認。

          整個 gdb 調(diào)試過程,如下:

          我來解釋下,上面的調(diào)試過程:

          1. 通過 info thread 打印了所有的線程信息,可以看到有 3 個線程,一個是主線程(LWP 87746),另外兩個都是我們自己創(chuàng)建的線程(LWP 87747 和 87748);

          2. 通過 thread 2,將切換到第 2 個線程(LWP 87748);

          3. 通過 bt,打印線程的調(diào)用棧信息,可以看到有 threadB_proc 函數(shù),說明這個是線程 B 函數(shù),也就說 LWP 87748 是線程 B;

          4. 通過 frame 3,打印調(diào)用棧中的第三個幀的信息,可以看到線程 B 函數(shù),在獲取互斥鎖 A 的時候阻塞了;

          5. 通過 p mutex_A,打印互斥鎖 A 對象信息,可以看到它被 LWP 為 87747(線程 A) 的線程持有著;

          6. 通過 p mutex_B,打印互斥鎖 A 對象信息,可以看到他被 LWP 為 87748 (線程 B) 的線程持有著;

          因為線程 B 在等待線程 A 所持有的 mutex_A, 而同時線程 A 又在等待線程 B 所擁有的mutex_B, 所以可以斷定該程序發(fā)生了死鎖。


          避免死鎖問題的發(fā)生

          前面我們提到,產(chǎn)生死鎖的四個必要條件是:互斥條件、持有并等待條件、不可剝奪條件、環(huán)路等待條件。

          那么避免死鎖問題就只需要破環(huán)其中一個條件就可以,最常見的并且可行的就是使用資源有序分配法,來破環(huán)環(huán)路等待條件

          那什么是資源有序分配法呢?

          線程 A 和 線程 B 獲取資源的順序要一樣,當線程 A 是先嘗試獲取資源 A,然后嘗試獲取資源  B 的時候,線程 B 同樣也是先嘗試獲取資源 A,然后嘗試獲取資源 B。也就是說,線程 A 和 線程 B 總是以相同的順序申請自己想要的資源。

          我們使用資源有序分配法的方式來修改前面發(fā)生死鎖的代碼,我們可以不改動線程 A 的代碼。

          我們先要清楚線程 A 獲取資源的順序,它是先獲取互斥鎖 A,然后獲取互斥鎖 B。

          所以我們只需將線程 B 改成以相同順序的獲取資源,就可以打破死鎖了。

          線程 B 函數(shù)改進后的代碼如下:

          執(zhí)行結果如下,可以看,沒有發(fā)生死鎖。


          總結

          簡單來說,死鎖問題的產(chǎn)生是由兩個或者以上線程并行執(zhí)行的時候,爭奪資源而互相等待造成的。

          死鎖只有同時滿足互斥、持有并等待、不可剝奪、環(huán)路等待這四個條件的時候才會發(fā)生。

          所以要避免死鎖問題,就是要破壞其中一個條件即可,最常用的方法就是使用資源有序分配法來破壞環(huán)路等待條件。


          推薦閱讀
          多個線程為了同個資源打起架來了,該如何讓他們安分?

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久久成人精品无码中文字幕 | 日韩一极片 | 永久官看美女裸体网站 | 特级西西444www大胆高清图片 | 青青草大香蕉在线视频 |