關(guān)于向主線程添加同步任務(wù)造成死鎖的思考

有這樣一個例子,即在主線程開啟同步任務(wù)死鎖的例子:

NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_main_queue(), ^{
   NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3

關(guān)于這個例子如何會死鎖,網(wǎng)上也有很詳細的解釋。不過可能對于某些基礎(chǔ)不是很扎實的同學來說,有些地方不太容易理解。這里,我說一下自己的理解,希望對你有所幫助。

如大家所說,造成這種死鎖的原因在于:

1.dispatch_sync,同步執(zhí)行;
2.dispatch_get_main_queue(),主隊列。這里先說一下為什么會造成死鎖,后面再介紹其他內(nèi)容;

第一點,先讓我們來看看dispatch_sync和dispatch_async,按照字面意思理解前者是同步的,后者是異步的。蘋果給出的文檔中,dispatch_sync的解釋是:Submits a block for synchronous execution on a dispatch queue。翻譯之后就是,向隊列中,提交一個同步執(zhí)行的block。同時,文檔中也有這樣一句話:dispatch_sync() will not return until the block has finished。就是說,只有當block中的內(nèi)容執(zhí)行完之后,才會返回之前插入的地方繼續(xù)執(zhí)行。

相對應(yīng)的,dispatch_async的解釋是:Submits a block for asynchronous execution on a dispatch queue。翻譯之后就是,向隊列中,提交一個異步執(zhí)行的block。同樣的,此時不會等待block的執(zhí)行,會直接執(zhí)行之后的代碼,而將block交給其他線程。這里暫且不說,后面再聊。

第二點,dispatch_get_main_queue(),蘋果給出的解釋是:Returns the default queue that is bound to the main thread。也就是說,它返回了依靠主線程來執(zhí)行任務(wù)的隊列。這里涉及到Runloop,簡單理解就是,iOS程序有一個一直在執(zhí)行的線程,這個線程會一直運行直到被叫停。這個線程和主隊列是綁定的,就是用來執(zhí)行主隊列的任務(wù)。

那么現(xiàn)在把它們放在一起考慮,系統(tǒng)一直在順序執(zhí)行主隊列的任務(wù)(通過主線程),此時阻塞主線程(dispatch_sync)向主隊列隊尾添加一個任務(wù)(不知道主隊列此時有沒有任務(wù)在執(zhí)行,不care)。dispatch_sync必須等到block執(zhí)行完才會返回當前線程(主線程)繼續(xù)往下執(zhí)行,當然下一個任務(wù)仍然來自于主隊列。那么,此時主線程在等待主隊列給出下一個任務(wù)(因為主線程與主隊列是綁定的,只能根據(jù)FIFO原則順序執(zhí)行主隊列的任務(wù));可是主隊列也在等待,它在等待主線程將block執(zhí)行完成才會給主線程另外一個任務(wù)。主線程和主隊列在互相等待,那么就造成了死鎖。

這個原理確實很繞口,在看了很多博客之后,總算有點兒眉目。本來我是無法理解為什么會造成死鎖的,直到想通了上面關(guān)節(jié),就是主線程和主隊列互相等待。在想通這些的過程中,我做了另外的工作來證實這種想法,或者說另外的這些讓我想通了這個關(guān)節(jié)。讓我們來看另外兩個例子。


這里有一個不會死鎖的例子:

    NSLog(@"1,NSThread:%@",[NSThread currentThread]); // 任務(wù)1
    dispatch_async(queueC, ^{
        NSLog(@"2,NSThread:%@",[NSThread currentThread]); // 任務(wù)2
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任務(wù)3
        });
        NSLog(@"4,NSThread:%@",[NSThread currentThread]); // 任務(wù)4
    });
    NSLog(@"5,NSThread:%@",[NSThread currentThread]); // 任務(wù)5

輸出結(jié)果是:

image.png

在這個例子中,用到了 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任務(wù)3 });然而并沒有出現(xiàn)線程死鎖現(xiàn)象,控制臺正常打印了1 5 2 3 4 。那么為什么這里不會造成死鎖呢?原因就在于dispatch_sync是阻塞了當前線程來給后面隊列添加任務(wù)。也就是說,在這里,dispatch_sync阻塞了number = 3 的線程,將block添加入主隊列,之后由主線程(與主隊列綁定)執(zhí)行打印任務(wù)。接著完成之后,再由*number = 3 *的線程執(zhí)行任務(wù)4,那么當然不會造成線程死鎖。


下面有一個會死鎖的例子:

    dispatch_queue_t queueS = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL); // 串行隊列
     NSLog(@"1,NSThread:%@",[NSThread currentThread]); // 任務(wù)1
    dispatch_async(queueS, ^{
        NSLog(@"2,NSThread:%@",[NSThread currentThread]); // 任務(wù)2
        dispatch_sync(queueS, ^{
            NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任務(wù)3
        });
        NSLog(@"4,NSThread:%@",[NSThread currentThread]); // 任務(wù)4
    });
    NSLog(@"5,NSThread:%@",[NSThread currentThread]); // 任務(wù)5

它的輸出是:

image.png

我們看到,它在執(zhí)行到 dispatch_sync(queueS, ^{ NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任務(wù)3 });的時候,出現(xiàn)了死鎖,回頭看一下它的打印結(jié)果,任務(wù)1和任務(wù)5都是主線程執(zhí)行的,而任務(wù)2是number = 3 的線程執(zhí)行的,也就是說,dispatch_sync阻塞了number = 3 的線程,同時,這個線程執(zhí)行隊列queueS里面的任務(wù)**。這就等價于在主線程向主隊列同步插入block造成死鎖,因為線程和隊列相互等待。


弄清楚線程和隊列的關(guān)系,這個問題就變得很簡單了不是嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容