如何安全使用dispatch_sync

概述

iOS開發(fā)者在與線程打交道的方式中族沃,使用最多的應(yīng)該就是GCD框架了频祝,沒有之一泌参。GCD將繁瑣的線程抽象為了一個個隊列,讓開發(fā)者極易理解和使用常空。但其實隊列的底層沽一,依然是利用線程實現(xiàn)的,同樣會有死鎖的問題漓糙。本文將探討如何規(guī)避disptach_sync接口引入的死鎖問題铣缠。


GCD基礎(chǔ)

GCD最基礎(chǔ)的兩個接口

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

第一個參數(shù)queue為隊列對象,第二個參數(shù)block為block對象昆禽。這兩個接口可以將任務(wù)block扔到隊列queue中去執(zhí)行蝗蛙。

開發(fā)者使用最頻繁的,就是在子線程環(huán)境下醉鳖,需要做UI更新時捡硅,我們可以將任務(wù)扔到主線程去執(zhí)行,

dispatch_sync(dispatch_get_main_queue(), block);
dispatch_async(dispatch_get_main_queue(), block);

dispatch_sync(dispatch_get_main_queue(), block)有可能引入死鎖的問題盗棵。

async VS. sync

disptach_async是異步扔一個blockqueue中壮韭,即扔完我就不管了,繼續(xù)執(zhí)行我的下一行代碼纹因。實際上當(dāng)下一行代碼執(zhí)行時喷屋,這個block還未執(zhí)行,只是入了隊列queue瞭恰,queue會排隊來執(zhí)行這個block屯曹。

disptach_sync則是同步扔一個blockqueue中,即扔了我就等著惊畏,等到queue排隊把這個block執(zhí)行完了之后是牢,才繼續(xù)執(zhí)行下一行代碼。


為什么要使用sync

disptach_sync主要用于代碼上下文對時序有強要求的場景陕截。簡單點說驳棱,就是下一行代碼的執(zhí)行,依賴于上一行代碼的結(jié)果农曲。例如說社搅,我們需要在子線程中讀取一個image對象,使用接口[UIImage imageNamed:]乳规,但imageNamed:實際上在iOS9以后才是線程安全的形葬,iOS9之前都需要在主線程獲取。所以暮的,我們需要從子線程切換到主線程獲取image笙以,然后再切回子線程拿到這個image

// ...currently in a subthread
__block UIImage *image;
dispatch_sync_on_main_queue(^{
    image = [UIImage imageNamed:@"Resource/img"];
});
attachment.image = image;

這里我們必須使用sync冻辩。


為什么會死鎖

假設(shè)當(dāng)前我們的代碼正在queue0中執(zhí)行猖腕。然后我們調(diào)用disptach_sync將一個任務(wù)block1扔到queue0中執(zhí)行拆祈,

// ... currently in queue0 or queue0's corresponding thread.
dispatch_sync(queue0, block1);

這時,dispatch_sync將等待queue0排隊執(zhí)行完block1倘感,然后才能繼續(xù)執(zhí)行下一行代碼放坏。But,當(dāng)前代碼執(zhí)行的環(huán)境也是queue0老玛。假設(shè)當(dāng)前執(zhí)行的任務(wù)為block0淤年。也就是說,block0在執(zhí)行到一半時蜡豹,需要等到自己的下一個任務(wù)block1執(zhí)行完麸粮,自己才能繼續(xù)執(zhí)行。而block1排隊在后面镜廉,需要等block0執(zhí)行完才能執(zhí)行豹休。這時死鎖就產(chǎn)生了,block0block1互相等待執(zhí)行桨吊,當(dāng)前線程就卡死在dispatch_sync這行代碼處威根。

我們發(fā)現(xiàn)的卡死問題,一般都是主線程死鎖视乐。一種較為常見的情況是洛搀,本身就已經(jīng)在主線程了,還同步向主線程扔了一個任務(wù):

// ... currently in the main thread
dispatch_sync(dispatch_get_main_queue(), block);

安全方法

YYKit中提供了一個同步扔任務(wù)到主線程的安全方法:

/**
 Submits a block for execution on a main queue and waits until the block completes.
*/
static inline void dispatch_sync_on_main_queue(void (^block)()) {
    if (pthread_main_np()) {
        block();
    } else {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

其方式就是在扔任務(wù)給主線程之前佑淀,先檢查當(dāng)前線程是否已經(jīng)是主線程留美,如果是,就不用調(diào)用GCD的隊列調(diào)度接口dispatch_sync了伸刃,直接執(zhí)行即可谎砾;如果不是主線程,那么調(diào)用GCD的dispatch_sync也不會卡死捧颅。

但事實上并不是這樣的景图,dispatch_sync_on_main_queue也可能會卡死,這個安全接口并不安全碉哑。這個接口只能保證兩個block之間不因互相等待而死鎖挚币。多于兩個block的互相依賴就束手無策了。

舉個例子扣典,假設(shè)queue0是一個子線程的隊列:

/* block0 */
// ... currently in the main thread.
dispatch_sync(queue0, ^{
    /* block1 */
    // ... currently in queue0's corresponding subthread.
    dispatch_sync_on_main_queue(^{
        /* block2 */
    });
});

在上述代碼中妆毕,block0正在主線程中執(zhí)行,并且同步等待子線程執(zhí)行完block1贮尖。block1又同步等待主線程執(zhí)行完block2笛粘。而當(dāng)前主線程正在執(zhí)行block0,即block2的執(zhí)行需要等到block0執(zhí)行完。這樣就成了block0-->block1-->block2-->block0...這樣一個循環(huán)等待薪前,即死鎖润努。由于block1的環(huán)境是子線程,所以安全API的線程判斷不起任何作用序六。

另舉一個例子:

/* block0 */
// ... currently in the main thread.
[[NSNotificationCenter defaultCenter] postNotificationName:@"aNotification" object:nil];

// ... in another context
[[NSNotificationCenter defaultCenter] addObserverForName:@"aNotification"
                                                  object:nil
                                                   queue:queue0
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  /* block1 */
                                                  // ... currently in queue0's corresponding subthread.
                                                  dispatch_sync_on_main_queue(^{
                                                      /* block2 */
                                                  });
                                              }];

由于通知NSNotification的執(zhí)行是同步的,這里會出現(xiàn)和上一例一樣的死鎖情況:block0-->block1-->block2-->block0...


如何定位死鎖問題

1.死鎖監(jiān)測和堆棧上報機制

要定位死鎖的問題蚤吹,我們需要知道在哪一行代碼上死鎖了例诀,以及為什么會出現(xiàn)死鎖。通常只要知道哪一行代碼死鎖了裁着,我們就能通過代碼分析出問題所在了繁涂。所以,如果死鎖的時候二驰,我們能夠把堆棧上報上來扔罪,就能知道哪一行代碼死鎖了。這里需要有完善的死鎖監(jiān)測和堆棧上報機制桶雀。

2.打印日志

如果暫時沒有人力或者技術(shù)支撐你去搭建完善的死鎖監(jiān)測和堆棧上報機制矿酵,那么你可以做一件簡單的事情以協(xié)助你定位問題,那就是打印日志矗积。在dispatch_sync或者加鎖之前全肮,打印一條日志。這樣在用戶反饋問題棘捣,或者測試重現(xiàn)問題的時候辜腺,提取日志便可分析出卡死的代碼處。


如何安全使用dispatch_sync

答案是乍恐,盡量不要使用评疗。沒有哪一個接口是可以保證絕對安全的。必須要使用dispatch_sync的時候茵烈,盡量使用dispatch_sync_on_main_queue這個API百匆。


若有發(fā)現(xiàn)問題,或是更好的建議呜投,歡迎私信或者評論:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胧华,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宙彪,更是在濱河造成了極大的恐慌矩动,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件释漆,死亡現(xiàn)場離奇詭異悲没,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門示姿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甜橱,“玉大人,你說我怎么就攤上這事栈戳∑癜粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵子檀,是天一觀的道長镊掖。 經(jīng)常有香客問我,道長褂痰,這世上最難降的妖魔是什么亩进? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮缩歪,結(jié)果婚禮上归薛,老公的妹妹穿的比我還像新娘。我一直安慰自己匪蝙,他們只是感情好主籍,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逛球,像睡著了一般崇猫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上需忿,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天诅炉,我揣著相機與錄音,去河邊找鬼屋厘。 笑死涕烧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汗洒。 我是一名探鬼主播议纯,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溢谤!你這毒婦竟也來了瞻凤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤世杀,失蹤者是張志新(化名)和其女友劉穎阀参,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瞻坝,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蛛壳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衙荐。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡捞挥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忧吟,到底是詐尸還是另有隱情砌函,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布溜族,位于F島的核電站讹俊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斩祭。R本人自食惡果不足惜劣像,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一乡话、第九天 我趴在偏房一處隱蔽的房頂上張望摧玫。 院中可真熱鬧,春花似錦绑青、人聲如沸诬像。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坏挠。三九已至,卻和暖如春邪乍,著一層夾襖步出監(jiān)牢的瞬間降狠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工庇楞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榜配,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓吕晌,卻偏偏與公主長得像蛋褥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子睛驳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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