strace命令,是Linux提供的跟蹤系統調用的命令,需要sudo或root權限,可以查看進程(線程)使用的系統調用。
基本用法:sudo strace -p 進程號
如果一個線程遞歸獲取同一個鎖,或者多個線程以不同的順序獲取多個鎖,那麼就會導致至少有一個線程在持有鎖的情況下再次等待在一個鎖上(持有的鎖和等待的鎖可能相同或不同),導致死鎖(deadlock)。
這時,至少有一個線程會等在futex()系統調用上,可以使用strace命令查看具體哪個線程等在哪個鎖上。
看圖中的例子,線程函數thread()連續兩次調用pthread_mutex_lock()加鎖g_mutex,導致遞歸死鎖。
這時它等在futex系統調用上,等待的鎖是g_mutex。
如果main函數裡打開注釋的pthread_join,那麼是等不到子線程返回的。
假設程序的文件名是a.out,在死鎖後:
1,首先使用ps -ef | grep a.out 查看所有的名字叫a.out的進程,如圖。
進程號2992的就是a.out,隨後的2609表示的是父進程,即啟動它的shell進程的pid。
2,使用top -H -p 2992查看該進程下的所有線程,這裡只有2個線程,其中與2992同號的是主線程。
top -H -p 進程號,可以查看所有的線程,其中%CPU顯示該線程的CPU使用率,越活躍的線程這個值越大。
這裡只是為了獲取所有的線程號。
3,然後用sudo strace -p 線程號,去跟蹤它的系統調用。
2992調用了nanosleep ()函數,納秒休眠,與我們在函數裡寫的while (1) sleep(1);代碼相符合。
2993則調用了futex()函數,且等待的鎖的地址是0x557a41002040。
4,這時,可以在鎖的初始化的地方,列印鎖的地址,這裡是main函數的第24行,列印的地址對應的鎖為g_mutex。
然後我們可以查看代碼裡使用了g_mutex加鎖的地方,可以確定是16、17行的兩次加鎖導致的。
這麼幾行代碼,是寫不出這問題的:(
只有代碼複雜起來之後,在一定條件下概率觸發的死鎖,才是實際項目裡的常見情況。
這時,strace命令直接確定死鎖的線程和鎖的地址的作用,才可以發揮出來。
只要在步驟4的調試信息中列印了可能導致死鎖的鎖地址,在問題再次出現時就可以用strace跟蹤到目標鎖的變量,進而查到錯誤代碼的位置。
像上圖那麼一改,是不是BUG就不顯眼了:( 捂臉
main函數第43行的continue會跳過45行的解鎖,導致第40行遞歸死鎖,和thread函數的第19行死鎖。
它是個概率問題,因為100是1000的因子,只有thread線程把g_count++到正好是100的倍數,然後在它下一次++之前main線程獲取了鎖,執行到第42行的if條件,才會出現continue越過45行的解鎖,然後死鎖。
下圖的ctrl+c卡斷的地方,就是出現了死鎖的例子。
PS:如果把第42行的100改成500,降低if成立的概率,那麼死鎖的概率會降低。
如果在thread函數的while循環裡加usleep睡眠,那麼相當於提高了main函數獲得鎖的成功率,死鎖的概率會提高。
這兩個辦法可以在調試時採用。
用戶態程序,其實不怎麼怕死鎖,而是怕該鎖的時候沒鎖,把malloc申請的內存破壞了,導致下一次不知道哪裡free或malloc的時候程序掛了。
這屬於多線程+競爭條件+野指針,C語言裡最無語的BUG之一。
另一個是內核的自旋鎖死鎖,只要鎖住一塊CPU,就會鎖住所有CPU,然後printk()列印不了信息了。