本文主要舉例說明GCD里的死鎖場景,分析造成死鎖的原因以及解決方案
在開始說GCD死鎖之前,我們先了解一下GCD的中的任務派發(fā)和隊列。
任務派發(fā)
任務派發(fā)方式 | 說明 |
---|---|
dispatch_sync() | 同步執(zhí)行,完成了它預定的任務后才返回,阻塞當前線程 |
dispatch_async() | 異步執(zhí)行,會立即返回,預定的任務會完成但不會等它完成,不阻塞當前線程 |
隊列種類
隊列種類 | 說明 |
---|---|
串行隊列 | 每次只能執(zhí)行一個任務,并且必須等待前一個執(zhí)行任務完成 |
并發(fā)隊列 | 一次可以并發(fā)執(zhí)行多個任務,不必等待執(zhí)行中的任務完成 |
GCD隊列種類
GCD隊列種類 | 獲取方法 | 隊列類型 | 說明 |
---|---|---|---|
主隊列 | dispatch_get_main_queue | 串行隊列 | 主線中執(zhí)行 |
全局隊列 | dispatch_get_global_queue | 并發(fā)隊列 | 子線程中執(zhí)行 |
用戶隊列 | dispatch_queue_create | 串并都可以 | 子線程中執(zhí)行 |
GCD死鎖
在GCD中,主要的死鎖就是當前串行隊列里面同步執(zhí)行當前串行隊列。解決的方法就是將同步的串行隊列放到另外一個線程執(zhí)行。
死鎖場景
1. 死鎖場景: 主線程調(diào)用主線程
- (void)deadLockCase1 {
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
}
控制臺輸出:
1
原因:
從控制臺輸出可以看出,任務2和任務3沒有執(zhí)行,此時已經(jīng)死鎖了。
因為dispatch_sync是同步的,本身就會阻塞當前線程,此刻阻塞了主線程。而當前block又在等待主線程執(zhí)行完畢,從而形成了主線程等待主線程,自己等自己的情況,形成了死鎖。
解決方法:
- 改用異步dispatch_async執(zhí)行
NSLog(@"1"); // 任務1
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
控制臺輸出:
1
3
2
- 不在主線程中運行,而是放在子線程中
NSLog(@"1"); // 任務1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務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"); //任務1
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"2"); //任務2
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"3"); //任務3
});
NSLog(@"4"); //任務4
});
NSLog(@"5"); //任務5
}
控制臺輸出:
1
2
原因:
從控制臺輸出結(jié)果來看,執(zhí)行到任務2后,就已經(jīng)死鎖了。因為該例子中兩個GCD都是使用的同步方式,而且還是同一個串行隊列,這就導致了和上一個例子一樣,自己在等待自己的情況,形成了死鎖。
解決方法:
- 將第二個GCD改為異步
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任務1
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"2"); //任務2
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"3"); //任務3
});
NSLog(@"4"); //任務4
});
NSLog(@"5"); //任務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"); //任務1
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"2"); //任務2
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"3"); //任務3
});
NSLog(@"4"); //任務4
});
NSLog(@"5"); //任務5
}
控制臺輸出:
1
5
2
原因:
雖然第一個GCD是異步的,但是第二個GCD是同步的,第二個GCD在等著第一個GCD結(jié)束,而第一個GCD的block又在等著第一個GCD結(jié)束,這樣就形成了死鎖。
注:對于以上將第二個GCD改為異步,第一個GCD為同步的場景,不會造成死鎖,是因為第二個GCD為異步,它不用等待第一個GCD執(zhí)行完畢,它和第一個GCD是沒有同步關系的。它是在第一個GCD執(zhí)行的同時并發(fā)執(zhí)行自己block的代碼。
- 將兩個GCD都改為異步
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任務1
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"2"); //任務2
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"3"); //任務3
});
NSLog(@"4"); //任務4
});
NSLog(@"5"); //任務5
}
控制臺輸出:
1
5
2
4
3
- 使用不同的串行隊列
- (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"); //任務1
dispatch_sync(aSerialDispatchQueue1, ^{
NSLog(@"2"); //任務2
dispatch_sync(aSerialDispatchQueue2, ^{
NSLog(@"3"); //任務3
});
NSLog(@"4"); //任務4
});
NSLog(@"5"); //任務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");
}
原因:
如果當前執(zhí)行的線程是主線程,以上代碼就會出現(xiàn)死鎖。
因為dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
阻塞了當前線程,而且等待時間是DISPATCH_TIME_FOREVER
——永遠等待,這樣它就永遠的阻塞了當前線程——主線程。導致主線中的dispatch_semaphore_signal(semaphore)
沒有執(zhí)行,
而dispatch_semaphore_wait
一直在等待dispatch_semaphore_signal
改變信號量,這樣就形成了死鎖。
解決方法:
應該將信號量移到并行隊列中,如全局調(diào)度隊列。以下場景,移到串行隊列也是可以的。但是串行隊列還是有可能死鎖的(如果執(zhí)行dispatch_semaphore_signal
方法還是在對應串行隊列中的話,即之前提到的串行隊列嵌套串行隊列的場景)。
- (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");
});
}