【linux】Valgrind工具集詳解(十三):Helgrind(執行緒錯誤檢測器)
阿新 • • 發佈:2018-11-22
一、概述
Helgrind用於檢測C、C ++和Fortran程式中使用符合POSIX標準的執行緒函式造成的同步錯誤。
POSIX中關於執行緒的主要抽象描述有:共享公共地址空間的一組執行緒、執行緒建立、執行緒連線、執行緒退出、互斥(鎖)、條件變數(執行緒間事件通知)、讀寫器鎖、自旋鎖、訊號量和執行緒等待(也叫做屏障)。
Helgrind可以檢測到三類錯誤:
- 錯誤使用POSIX執行緒API;
- 死鎖問題;
- 資源競爭——在沒有足鎖定或同步的情況下訪問記憶體。
像這樣的問題經常導致不可重現的、與時間相關的崩潰、死鎖等很難通過其他方式找到。
二、使用
編譯: gcc -g -pthread main.c
三、錯誤資訊詳解
1、錯誤使用POSIX執行緒API
Helgrind檢查許多POSIX執行緒函式的呼叫,因此能夠報告各種常見問題。雖然有很多都是無意義的錯誤,但它們可能會導致程式行為不明確,以及以後難以發現的錯誤。檢測到的錯誤有以下幾種:
- 解鎖無效的互斥鎖,錯誤資訊如下
==10045== Thread #1 unlocked an invalid lock at 0xFFEFFFBA0 ==10045== at 0x4C329D6: pthread_mutex_unlock (hg_intercepts.c:707) ==10045== by 0x4009D7: main (main.c:28)
- 解鎖未鎖定的互斥鎖
==10045== Thread #1 unlocked a not-locked lock at 0x6010E0 ==10045== at 0x4C329D6: pthread_mutex_unlock (hg_intercepts.c:707) ==10045== by 0x4009F0: main (main.c:31) ==10045== Lock at 0x6010E0 was first observed ==10045== at 0x4C321AA: pthread_mutex_init (hg_intercepts.c:518) ==10045== by 0x4009E6: main (main.c:30) ==10045== Address 0x6010e0 is 0 bytes inside data symbol "g_lock"
- 解鎖由不同執行緒持有的互斥鎖
- 銷燬無效或鎖定的互斥鎖
==10073== Thread #1: pthread_mutex_destroy of a locked mutex
==10073== at 0x4C32268: pthread_mutex_destroy (hg_intercepts.c:553)
==10073== by 0x4009D9: main (main.c:43)
- 遞迴鎖定非遞迴互斥鎖
- 釋放包含鎖定互斥鎖的記憶體
- 錯誤的將pthread_mutex_t型別引數傳遞給了本應該是pthread_ rwlock_t引數的函式,反之亦然
- 當POSIX執行緒函式呼叫失敗並且必須處理錯誤程式碼時
- 當一個執行緒退出,而持有的鎖仍然處於鎖定狀態時
- pthread_cond_wait 使用了未鎖定的互斥鎖、無效的互斥鎖或由其他執行緒鎖定的互斥鎖時
- 繫結條件變數互斥鎖和使用的鎖不一致
- 無效或重複初始化執行緒等待(如pthread_barrier_init)
- 在pthread_barrier_init或pthread_barrier_wait之前呼叫了pthread_barrier_destroy
- 在pthread_barrier_init之前呼叫了pthread_barrier_wait
- 由系統執行緒庫返回的錯誤程式碼,即使Helgrind本身未檢測到錯誤
2、死鎖
造成死鎖最常見的原因如下:
假設鎖L1和L2在同時鎖定的狀態下才能訪問共享資源R。如果執行緒T1和T2都要訪問R,T1首先獲取L1、T2首先獲取L2,然後T1因為嘗試獲取L2失敗而進入於等待狀態,T2因為嘗試獲取L1失敗也進入等待狀態,這時就造成死鎖。
Helgrind檢查死鎖的原理:
Helgrind首先構建一個有向圖,指示過去獲取鎖的順序。當執行緒獲取新鎖時,圖表會更新,然後檢查它是否現在包含一個迴圈。迴圈的存在表明涉及迴圈中的鎖可能是死鎖。
通常,Helgrind將選擇可能造成死鎖的兩個鎖,並將相關的資訊打印出來。
==8039== Thread #1: lock order "0x6010E0 before 0x601120" violated
==8039==
==8039== Observed (incorrect) order is: acquisition of lock at 0x601120
==8039== at 0x4C32536: pthread_mutex_lock (hg_intercepts.c:593)
==8039== by 0x4009BB: main (main.c:41)
==8039==
==8039== followed by a later acquisition of lock at 0x6010E0
==8039== at 0x4C32536: pthread_mutex_lock (hg_intercepts.c:593)
==8039== by 0x4009C5: main (main.c:42)
==8039==
==8039== Required order was established by acquisition of lock at 0x6010E0
==8039== at 0x4C32536: pthread_mutex_lock (hg_intercepts.c:593)
==8039== by 0x4008EC: fun (main.c:16)
==8039== by 0x4C30FA6: mythread_wrapper (hg_intercepts.c:234)
==8039== by 0x4E45183: start_thread (pthread_create.c:312)
==8039== by 0x515903C: clone (clone.S:111)
==8039==
==8039== followed by a later acquisition of lock at 0x601120
==8039== at 0x4C32536: pthread_mutex_lock (hg_intercepts.c:593)
==8039== by 0x4008F6: fun (main.c:17)
==8039== by 0x4C30FA6: mythread_wrapper (hg_intercepts.c:234)
==8039== by 0x4E45183: start_thread (pthread_create.c:312)
==8039== by 0x515903C: clone (clone.S:111)
==8039==
==8039== Lock at 0x6010E0 was first observed
==8039== at 0x4C321AA: pthread_mutex_init (hg_intercepts.c:518)
==8039== by 0x40096F: main (main.c:30)
==8039== Address 0x6010e0 is 0 bytes inside data symbol "g_lock1"
==8039==
==8039== Lock at 0x601120 was first observed
==8039== at 0x4C321AA: pthread_mutex_init (hg_intercepts.c:518)
==8039== by 0x40097E: main (main.c:31)
==8039== Address 0x601120 is 0 bytes inside data symbol "g_lock2"
3、資源競爭
當兩個執行緒訪問共享記憶體位置而不使用合適的鎖或其他同步方法來確保,同一時刻只用單一執行緒訪問共享記憶體時,就可能發生資源競爭。