資源競爭與死鎖檢測

多線程編程一直是一個非常難的話題寝姿,而資源競爭和死鎖問題則是比較常見的多線程問題嗡综,這里我們來看看如何檢測這些問題。

LLVM

其實llvm項目自身就有這兩者的檢測方法只壳。而在xcode中也集成了該功能俏拱,要使用也非常簡單,選中Thread Sanitizer吼句,并且重新編譯運行即可锅必。

image

那么接下來我們來看看使用情況以及他們是如何實現(xiàn)的。

Data Race

數(shù)據(jù)競爭是我們非常容易犯的一個錯誤惕艳,而且出現(xiàn)問題了也非常難解決搞隐。因為出現(xiàn)的概率并不高,而且出現(xiàn)了問題也不會直接表現(xiàn)出來远搪,而可能是通過其他方式表現(xiàn)出來劣纲。

首先我們來看一個非常簡單的數(shù)據(jù)競爭問題:

char g_char;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self setCharB];
    });
    [self setCharA];
}

- (void)setCharA {
    g_char = 'a';
}

- (void)setCharB {
    g_char = 'b';
}

雖然更新一個字節(jié)這種操作非常簡單,但依然需要在這里加上鎖谁鳍,如果沒有加上則會報告如下錯誤:

==================
WARNING: ThreadSanitizer: data race (pid=62345)
  Write of size 1 at 0x0001032e0f10 by thread T2:
    #0 -[ViewController setCharB] ViewController.m:35 (MallocTest:x86_64+0x100001499)
    #1 __29-[ViewController viewDidLoad]_block_invoke ViewController.m:26 (MallocTest:x86_64+0x1000013da)
    #2 __tsan::invoke_and_release_block(void*) <null>:2136816 (libclang_rt.tsan_iossim_dynamic.dylib:x86_64+0x622bb)
    #3 _dispatch_client_callout <null>:2136816 (libdispatch.dylib:x86_64+0x3847)

  Previous write of size 1 at 0x0001032e0f10 by main thread:
    #0 -[ViewController setCharA] ViewController.m:32 (MallocTest:x86_64+0x100001472)
    #1 -[ViewController viewDidLoad] ViewController.m:28 (MallocTest:x86_64+0x100001381)
    #2 -[UIViewController loadViewIfRequired] <null>:2136816 (UIKit:x86_64+0x1ce190)
    #3 start <null>:2136816 (libdyld.dylib:x86_64+0x1954)

  Location is global 'g_char' at 0x0001032e0f10 (MallocTest+0x000100003f10)

  Thread T2 (tid=1422519, running) is a GCD worker thread

SUMMARY: ThreadSanitizer: data race ViewController.m:35 in -[ViewController setCharB]
==================

同時在左邊的導航欄里會顯示如下結果:

image

那么LLVM是怎么實現(xiàn)的呢癞季?

資源競爭的檢測其實分為兩部分,一部分是編譯期的處理倘潜,另一部分是運行期的監(jiān)控绷柒。

編譯期,編譯器會在數(shù)據(jù)訪問的時候插入一段代碼窍荧,來告訴檢測器具體的數(shù)據(jù)訪問情況辉巡。這個效果可以看具體的匯編:

-[ViewController setCharA]:
    0x106bd4448 <+0>:  pushq  %rbp
    0x106bd4449 <+1>:  movq   %rsp, %rbp
    0x106bd444c <+4>:  movq   0x8(%rbp), %rdi
    0x106bd4450 <+8>:  callq  0x106bd4798               ; symbol stub for: __tsan_func_entry
    0x106bd4455 <+13>: leaq   0x2afc(%rip), %rdi        ; g_char
    0x106bd445c <+20>: callq  0x106bd47bc               ; symbol stub for: __tsan_write1
    0x106bd4461 <+25>: movb   $0x61, 0x2af0(%rip)       ; lock + 63
    0x106bd4468 <+32>: callq  0x106bd479e               ; symbol stub for: __tsan_func_exit
    0x106bd446d <+37>: popq   %rbp
    0x106bd446e <+38>: retq   

運行期的監(jiān)控則是靠動態(tài)庫來導入的(在早期是依賴于靜態(tài)庫)。

可以看到蕊退,需要做到在編譯期插入代碼郊楣,不禁會想已經(jīng)編譯好的二進制該怎么辦憔恳?這里我們來看兩個例子:

CoreFoundation`-[__NSArrayM addObject:]:
    ...
    0x10e5b0d82 <+18>: leaq   0x3a3fa7(%rip), %rax      ; __cf_tsanWriteFunction
    ...

在NSMutableArray的代碼中,我們發(fā)現(xiàn)有一個方法很可疑__cf_tsanWriteFunction净蚤,這個方法似乎就是上面的__tsan_write1方法的objc版钥组。同時這個方法在真機上是沒有的。

pthread_mutex_lock(&lock)在該模式下實際對應的方法是libclang_rt.tsan_iossim_dynamic.dylib wrap_pthread_mutex_lock今瀑,同時dispatch_sync對應的方法是libclang_rt.tsan_iossim_dynamic.dylib wrap_dispatch_sync程梦,可以知道他們都來源于一個非標準的動態(tài)庫,這也就是說明在該模式下橘荠,系統(tǒng)會給我們鏈接一個已經(jīng)編譯好的屿附,插入相應代碼的動態(tài)庫。這也代表著如果你引用了第三方二進制庫哥童,不一定能夠檢測出其中的競爭問題挺份。

這里還需要檢測到線程的狀態(tài),則是使用了pthread的一個公開接口:

typedef void (*pthread_introspection_hook_t)(unsigned int event, pthread_t thread, void *addr, size_t size);

enum {
  PTHREAD_INTROSPECTION_THREAD_CREATE = 1,
  PTHREAD_INTROSPECTION_THREAD_START,
  PTHREAD_INTROSPECTION_THREAD_TERMINATE,
  PTHREAD_INTROSPECTION_THREAD_DESTROY,
};

pthread_introspection_hook_install(pthread_introspection_hook);
算法

這個的檢測算法較為復雜贮懈,這里簡單的來描述一下匀泊。

  • 首先每一個數(shù)據(jù)根據(jù)其內(nèi)存地址與訪問線程id都會有一個對應的內(nèi)存區(qū)塊來保存其訪問數(shù)據(jù),一般是8 bytes映射為1 bytes朵你,所以這里的內(nèi)存分配器也是需要進行相應的修改各聘。
  • 將當前狀態(tài)和已保存的數(shù)據(jù)進行比較。
  • 如果是非同一個線程抡医,并且已保存的數(shù)據(jù)訪問時間是在當前訪問時間之后躲因。
  • 那么認為這是一次資源競爭。

Dead lock

死鎖的檢測相對比較簡單了魂拦,他并不需要編譯期的介入毛仪,而是純運行時的檢測。不過遺憾的是xcode上并沒有集成芯勘,可能是覺得死鎖本身就會嚴重阻礙程序運行箱靴,容易被察覺吧。

主要需要做的是hook掉所有鎖相關的api荷愕,掌管willLockdidLock的消息衡怀,LLVM提供默認hook了pthread的相關接口。

每次加鎖之前都會產(chǎn)生一個鎖-線程的匹配安疗,加鎖之后釋放該鎖-線程的匹配抛杨。

如果A鎖被某線程持有,同時B鎖也被該線程持有荐类,那么就形成了A=>B的一個關聯(lián)怖现,如果這樣的關聯(lián)形成了一個環(huán),那么就說明產(chǎn)生了死鎖。該方法可以利用鄰接二維矩陣來實現(xiàn)高效的查找屈嗤。

如果恢復產(chǎn)生的死鎖問題呢潘拨?這里我沒有找到更好的辦法,只能做以下兩種處理:

  • 殺死某個非主線程的線程饶号,這樣能夠解除死鎖铁追,但會引起資源泄露和邏輯缺失的問題。
  • 直接返回茫船,可能會引起資源競爭的問題琅束。

參考

The "Double-Checked Locking is Broken" Declaration
Finding races and memory errors with compiler instrumentation.
ThreadSanitizerAlgorithm
llvm-compiler-rt
valgrind

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市算谈,隨后出現(xiàn)的幾起案子涩禀,更是在濱河造成了極大的恐慌,老刑警劉巖濒生,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埋泵,死亡現(xiàn)場離奇詭異幔欧,居然都是意外死亡罪治,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門礁蔗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來觉义,“玉大人,你說我怎么就攤上這事浴井∩购В” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵磺浙,是天一觀的道長洪囤。 經(jīng)常有香客問我,道長撕氧,這世上最難降的妖魔是什么瘤缩? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮伦泥,結果婚禮上剥啤,老公的妹妹穿的比我還像新娘。我一直安慰自己不脯,他們只是感情好府怯,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著防楷,像睡著了一般牺丙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上复局,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天冲簿,我揣著相機與錄音是整,去河邊找鬼。 笑死民假,一個胖子當著我的面吹牛浮入,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羊异,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼事秀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了野舶?” 一聲冷哼從身側響起易迹,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎平道,沒想到半個月后睹欲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡一屋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年窘疮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀墨。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡闸衫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诽嘉,到底是詐尸還是另有隱情蔚出,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布虫腋,位于F島的核電站骄酗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悦冀。R本人自食惡果不足惜趋翻,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雏门。 院中可真熱鬧嘿歌,春花似錦、人聲如沸茁影。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽募闲。三九已至步脓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背靴患。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工仍侥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸳君。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓农渊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親或颊。 傳聞我的和親對象是個殘疾皇子砸紊,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 引用自多線程編程指南應用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時修改同一資源有...
    Mitchell閱讀 1,990評論 1 7
  • 2016年國慶假期終于把此書過完,整理筆記和體會于此平挑。 關于書名 書名源于俄羅斯的演員斯坦尼斯拉夫斯基創(chuàng)作的《演員...
    李劍飛的簡書閱讀 7,241評論 2 65
  • 翻譯:Synchronization 同步 應用程序中存在多個線程會導致潛在的問題游添,這些問題可能會導致從多個執(zhí)行線...
    AlexCorleone閱讀 2,491評論 0 4
  • 突如其來的暴雪,讓格拉斯哥這座已經(jīng)快二十年沒有下過大雪的城市措手不及通熄。 在前一天的夜晚就已經(jīng)發(fā)現(xiàn)了端倪唆涝,明明應該已...
    無與倫比的機智閱讀 285評論 0 2
  • 曾經(jīng)以為我長大了石抡,懂事了,可以做好很多事情助泽,也可以承擔起很多責任。但嚎京,我還是太幼稚了嗡贺。其實,我一直都還是那個懵懂無...
    雪雪Y閱讀 382評論 0 1