說說GCD中的死鎖

本文主要舉例說明GCD里的死鎖場景,分析造成死鎖的原因以及解決方案

在開始說GCD死鎖之前,我們先了解一下GCD的中的任務(wù)派發(fā)和隊列。

任務(wù)派發(fā)
任務(wù)派發(fā)方式 說明
dispatch_sync() 同步執(zhí)行,完成了它預(yù)定的任務(wù)后才返回,阻塞當(dāng)前線程
dispatch_async() 異步執(zhí)行,會立即返回,預(yù)定的任務(wù)會完成但不會等它完成,不阻塞當(dāng)前線程
隊列種類
隊列種類 說明
串行隊列 每次只能執(zhí)行一個任務(wù),并且必須等待前一個執(zhí)行任務(wù)完成
并發(fā)隊列 一次可以并發(fā)執(zhí)行多個任務(wù),不必等待執(zhí)行中的任務(wù)完成
GCD隊列種類
GCD隊列種類 獲取方法 隊列類型 說明
主隊列 dispatch_get_main_queue 串行隊列 主線中執(zhí)行
全局隊列 dispatch_get_global_queue 并發(fā)隊列 子線程中執(zhí)行
用戶隊列 dispatch_queue_create 串并都可以 子線程中執(zhí)行

GCD死鎖

在GCD中,主要的死鎖就是當(dāng)前串行隊列里面同步執(zhí)行當(dāng)前串行隊列。解決的方法就是將同步的串行隊列放到另外一個線程執(zhí)行。

死鎖場景

1. 死鎖場景: 主線程調(diào)用主線程
- (void)deadLockCase1 {
    NSLog(@"1"); // 任務(wù)1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任務(wù)2
    });
    NSLog(@"3"); // 任務(wù)3
}

控制臺輸出:

1
原因:

從控制臺輸出可以看出,任務(wù)2和任務(wù)3沒有執(zhí)行,此時已經(jīng)死鎖了。
因為dispatch_sync是同步的,本身就會阻塞當(dāng)前線程,此刻阻塞了主線程。而當(dāng)前block又在等待主線程執(zhí)行完畢,從而形成了主線程等待主線程,自己等自己的情況,形成了死鎖。

解決方法:
  1. 改用異步dispatch_async執(zhí)行
NSLog(@"1"); // 任務(wù)1
dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任務(wù)2
 });
NSLog(@"3"); // 任務(wù)3

控制臺輸出:

1
3
2 
  1. 不在主線程中運行,而是放在子線程中
NSLog(@"1"); // 任務(wù)1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3

控制臺輸出:

1
2
3

注:如果block中是刷新UI的操作,則不能放在子線程中執(zhí)行,會crash

死鎖場景2: (同步串行隊列嵌套自己)
- (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任務(wù)1
    dispatch_sync(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任務(wù)2
        dispatch_sync(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任務(wù)3
        });
        NSLog(@"4");  //任務(wù)4
    });
    NSLog(@"5");  //任務(wù)5
}

控制臺輸出:

1
2
原因:

從控制臺輸出結(jié)果來看,執(zhí)行到任務(wù)2后,就已經(jīng)死鎖了。因為該例子中兩個GCD都是使用的同步方式,而且還是同一個串行隊列,這就導(dǎo)致了和上一個例子一樣,自己在等待自己的情況,形成了死鎖。

解決方法:
  1. 將第二個GCD改為異步
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任務(wù)1
    dispatch_sync(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任務(wù)2
        dispatch_async(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任務(wù)3
        });
        NSLog(@"4");  //任務(wù)4
    });
    NSLog(@"5");  //任務(wù)5
}

控制臺輸出:

1
2
4
5
3

然而,將第一個GCD改為異步,不能解決問題

 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任務(wù)1
    dispatch_async(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任務(wù)2
        dispatch_sync(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任務(wù)3
        });
        NSLog(@"4");  //任務(wù)4
    });
    NSLog(@"5");  //任務(wù)5
}

控制臺輸出:

1
5
2
原因:

雖然第一個GCD是異步的,但是第二個GCD是同步的,第二個GCD在等著第一個GCD結(jié)束,而第一個GCD的block又在等著第一個GCD結(jié)束,這樣就形成了死鎖。
注:對于以上將第二個GCD改為異步,第一個GCD為同步的場景,不會造成死鎖,是因為第二個GCD為異步,它不用等待第一個GCD執(zhí)行完畢,它和第一個GCD是沒有同步關(guān)系的。它是在第一個GCD執(zhí)行的同時并發(fā)執(zhí)行自己block的代碼。

  1. 將兩個GCD都改為異步
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任務(wù)1
    dispatch_async(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任務(wù)2
        dispatch_async(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任務(wù)3
        });
        NSLog(@"4");  //任務(wù)4
    });
    NSLog(@"5");  //任務(wù)5
}

控制臺輸出:

1
5
2
4
3
  1. 使用不同的串行隊列
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue1 = dispatch_queue_create("com.test.deadlock.queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t aSerialDispatchQueue2 = dispatch_queue_create("com.test.deadlock.queue2", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任務(wù)1
    dispatch_sync(aSerialDispatchQueue1, ^{
        NSLog(@"2"); //任務(wù)2
        dispatch_sync(aSerialDispatchQueue2, ^{
            NSLog(@"3"); //任務(wù)3
        });
        NSLog(@"4");  //任務(wù)4
    });
    NSLog(@"5");  //任務(wù)5
}

控制臺輸出:

1
2
3
4
5
3. 死鎖場景: 信號量阻塞主線程
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"semaphore create!");
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(semaphore);
        NSLog(@"semaphore plus 1");
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore minus 1");
}
原因:

如果當(dāng)前執(zhí)行的線程是主線程,以上代碼就會出現(xiàn)死鎖。
因為dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了當(dāng)前線程,而且等待時間是DISPATCH_TIME_FOREVER——永遠(yuǎn)等待,這樣它就永遠(yuǎn)的阻塞了當(dāng)前線程——主線程。導(dǎo)致主線中的dispatch_semaphore_signal(semaphore)沒有執(zhí)行,
dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改變信號量,這樣就形成了死鎖。

解決方法:

應(yīng)該將信號量移到并行隊列中,如全局調(diào)度隊列。以下場景,移到串行隊列也是可以的。但是串行隊列還是有可能死鎖的(如果執(zhí)行dispatch_semaphore_signal方法還是在對應(yīng)串行隊列中的話,即之前提到的串行隊列嵌套串行隊列的場景)。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSLog(@"semaphore create!");
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(semaphore);
            NSLog(@"semaphore plus 1");
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore minus 1");
    });
}
最后編輯于
?著作權(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)容