GCD死鎖

死鎖

1、定義:

所謂死鎖,通常指有兩個線程T1和T2都卡住了,并等待對方完成某些操作。T1不能完成是因為它在等待T2完成。但T2也不能完成,因為它在等待T1完成。于是大家都完不成,就導(dǎo)致了死鎖(DeadLock)。

2、產(chǎn)生死鎖的條件:

產(chǎn)生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進(jìn)程使用。
(2) 請求與保持條件:一個進(jìn)程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。
(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之
一不滿足,就不會發(fā)生死鎖。

3、圖示:

image.png

首先來看一份導(dǎo)致死鎖的典型代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_sync(dispatch_get_main_queue(), ^(void){
            NSLog(@"這里死鎖了");
        });
    }
    return 0;
}

比如這個最簡單的OC命令行程序就會導(dǎo)致死鎖,運(yùn)行后不會看到任何結(jié)果。

基本概念

在解釋為什么會死鎖之前,首先明確一下“同步&異步”“串行&并發(fā)”這兩組基本概念:

1、同步&異步

1.1、同步

同步執(zhí)行:比如這里的dispatch_sync,這個函數(shù)會把一個block加入到指定的隊列中,而且會一直等到執(zhí)行完blcok,這個函數(shù)才返回。因此在block執(zhí)行完之前,調(diào)用dispatch_sync方法的線程是阻塞的。

1.2、異步

異步執(zhí)行:一般使用dispatch_async,這個函數(shù)也會把一個block加入到指定的隊列中,但是和同步執(zhí)行不同的是,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了。

1.3、關(guān)于這GCD兩個函數(shù)的強(qiáng)調(diào):

dispatch_async 和 dispatch_sync 他們的作用是將 任務(wù)(block)添加進(jìn)指定的隊列中。并根據(jù)是否為sync決定調(diào)用該函數(shù)的線程是否需要阻塞。
注意:這里調(diào)用該函數(shù)的線程并不執(zhí)行 參數(shù)中指定的任務(wù)(block塊),任務(wù)的執(zhí)行者是GCD分配給任務(wù)所在隊列的線程。
結(jié)論:調(diào)用dispatch_sync和dispatch_async的線程,并不一定是任務(wù)(block塊)的執(zhí)行者。

2、串行&并發(fā)(隊列)

2.1、串行隊列

串行隊列:比如這里的dispatch_get_main_queue。這個隊列中所有任務(wù),一定按照FIFO(先來后到的順序)執(zhí)行。不僅如此,還可以保證在執(zhí)行某個任務(wù)時,在它前面進(jìn)入隊列的所有任務(wù)肯定執(zhí)行完了。對于每一個不同的串行隊列,系統(tǒng)會為這個隊列建立唯一的線程來執(zhí)行代碼。

2.2、并發(fā)隊列

比如使用dispatch_get_global_queue。這個隊列中的任務(wù)也是按照FIFO(先來后到的順序)開始執(zhí)行,注意是開始,但是它們的執(zhí)行結(jié)束時間是不確定的,取決于每個任務(wù)的耗時。并發(fā)隊列中的任務(wù):GCD會動態(tài)分配多條線程來執(zhí)行。具體幾條線程取決于當(dāng)前內(nèi)存使用狀況,線程池中線程數(shù)等因素。

3、分析

回到上面的死鎖代碼中:首先明確的是:執(zhí)行這個dispatch_get_main_queue隊列的是主線程。執(zhí)行了dispatch_sync函數(shù)后,將block添加到了main_queue中,同時調(diào)用dispatch_syn這個函數(shù)的線程(也就是主線程)被阻塞,等待block執(zhí)行完成,而執(zhí)行主線程隊列任務(wù)的線程正是主線程,此時他處于阻塞狀態(tài),所以block永遠(yuǎn)不會被執(zhí)行,因此主線程一直處于阻塞狀態(tài)。因此這段代碼運(yùn)行后,并非卡在block中無法返回,而是根本無法執(zhí)行到這個block

4、總結(jié):

我們思考一下,什么情況下會導(dǎo)致死鎖:采用排除法:即先看看什么情況下不會發(fā)生死鎖。(先來討論異步情況,再討論同步情況。)
4.1 異步執(zhí)行block肯定不會發(fā)生死鎖。比如剛剛的代碼改成這樣:

dispatch_async(dispatch_get_global_queue(0,0), ^(void){
NSLog(@"這就不死鎖了");
});

甚至可以總結(jié)出來:異步執(zhí)行一定不會導(dǎo)致死鎖。因為回顧一下之前導(dǎo)致的死鎖的原因,很重要的一點是主線程在執(zhí)行dispatch_sync,這是個同步方法,block執(zhí)行完之前都不會返回。而既然是異步的執(zhí)行,那么是立刻返回的,因此不會阻塞主線程。雙向的阻塞不成立了,只是主線程處理blcok時阻塞,但這不會引起死鎖。
所以接下來就只需要重點思考一下,在同步執(zhí)行時,什么時候會導(dǎo)致死鎖。
4.2.1: 可以再得出一個結(jié)論,同步的向 并發(fā)隊列 中添加block不會導(dǎo)致死鎖。
原因如下:之前由于在串行隊列中添加了block,block一直等到前面的任務(wù)處理完才會執(zhí)行,從而導(dǎo)致了死鎖。如果采用同步的向并發(fā)隊列中添加block,首先需要明確的一點是同步方式是不會再開線程的,也就是說當(dāng)前的調(diào)用線程會去立即執(zhí)行block,直到block執(zhí)行完成后才會繼續(xù)向下執(zhí)行。但是因為是并發(fā)隊列,隊列中下一個任務(wù)的執(zhí)行不需要等待上一個任務(wù)的完成,所以即使添加到當(dāng)前調(diào)用任務(wù)的隊列也不會產(chǎn)生死鎖,當(dāng)前線程會立即執(zhí)行新添加的任務(wù),然后返回,并繼續(xù)向下執(zhí)行。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"11111====current thread :%@",[NSThread currentThread]);
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"222====current thread :%@",[NSThread currentThread]);
        });
        NSLog(@"333333333");
    });

結(jié)果:
[10324:5277752] 11111====current thread :<NSThread: 0x60400027f3c0>{number = 3, name = (null)}
[10324:5277752] 222====current thread :<NSThread: 0x60400027f3c0>{number = 3, name = (null)}
[10324:5277752] 333333333
我們可以看到,并發(fā)隊列中:同一條線程在上一個任務(wù)沒有完成的情況下,可以去執(zhí)行下一個任務(wù)。因為并發(fā)隊列中下一個任務(wù)的執(zhí)行不需要上一個任務(wù)執(zhí)行完成。

4.2.2: 最后討論下同步的向同步隊列中添加block,這種情況下會不會造成死鎖呢,答案是:不一定。
比如文章開頭的例子就屬于這種情況。如果同步的向另外一個串行隊列添加方法,并不一定導(dǎo)致死鎖。比如:

dispatch_queue_t queue = dispatch_queue_create("SimaSDK", nil);
dispatch_sync(queue, ^(void){
NSLog(@"這個也不會死鎖");
});

因為:SimaSDK這個隊列的執(zhí)行線程是主線程,同步方式不會開辟新線程,但這是我們是將任務(wù)添加到了SimaSDK這個隊列中,所以主線程會來立即執(zhí)行這個隊列中的任務(wù),執(zhí)行完成后就會返回,主線程不會繼續(xù)被阻塞。所以不會死鎖。

事實上,導(dǎo)致死鎖的原因一定是:

在某一個串行隊列中,同步的向這個隊列添加block。
同步的向串行隊列中添加

另外,因為隊列是可以嵌套的,比如在A隊列(串行)添加一個任務(wù)a,在a這個任務(wù)中向B隊列(串行)添加任務(wù)b,在b這個任務(wù)中又向A隊列添加任務(wù),這就間接滿足了“在某一個串行隊列中,同步的向這個隊列添加block”。但是我們好像每一次都沒有直接向相同的隊列中添加block。

所以:判斷是否發(fā)生死鎖的最好方法就是看有沒有在串行隊列(當(dāng)然也包括主隊列)中向這個隊列添加任務(wù)。

我們使用同步的方法編程,往往是要求保證任務(wù)之間的執(zhí)行順序是完全確定的。且不說GCD提供了很多強(qiáng)大的功能來滿足這個需求,向串行隊列中同步的添加任務(wù)本身就是不合理的,畢竟隊列已經(jīng)是串行的了,直接異步添加就可以了。

?著作權(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)容