本文主要舉例說明GCD里的死鎖場(chǎng)景,分析造成死鎖的原因以及解決方案
在開始說GCD死鎖之前沪猴,我們先了解一下GCD的中的任務(wù)派發(fā)和隊(duì)列璃吧。
任務(wù)派發(fā)
任務(wù)派發(fā)方式 | 說明 |
---|---|
dispatch_sync() | 同步執(zhí)行嫡纠,完成了它預(yù)定的任務(wù)后才返回递沪,阻塞當(dāng)前線程 |
dispatch_async() | 異步執(zhí)行,會(huì)立即返回沫勿,預(yù)定的任務(wù)會(huì)完成但不會(huì)等它完成挨约,不阻塞當(dāng)前線程 |
隊(duì)列種類
隊(duì)列種類 | 說明 |
---|---|
串行隊(duì)列 | 每次只能執(zhí)行一個(gè)任務(wù),并且必須等待前一個(gè)執(zhí)行任務(wù)完成 |
并發(fā)隊(duì)列 | 一次可以并發(fā)執(zhí)行多個(gè)任務(wù)产雹,不必等待執(zhí)行中的任務(wù)完成 |
GCD隊(duì)列種類
GCD隊(duì)列種類 | 獲取方法 | 隊(duì)列類型 | 說明 |
---|---|---|---|
主隊(duì)列 | dispatch_get_main_queue | 串行隊(duì)列 | 主線中執(zhí)行 |
全局隊(duì)列 | dispatch_get_global_queue | 并發(fā)隊(duì)列 | 子線程中執(zhí)行 |
用戶隊(duì)列 | dispatch_queue_create | 串并都可以 | 子線程中執(zhí)行 |
GCD死鎖
在GCD中诫惭,主要的死鎖就是當(dāng)前串行隊(duì)列里面同步執(zhí)行當(dāng)前串行隊(duì)列。解決的方法就是將同步的串行隊(duì)列放到另外一個(gè)線程執(zhí)行蔓挖。
死鎖場(chǎng)景
1. 死鎖場(chǎng)景: 主線程調(diào)用主線程
- (void)deadLockCase1 {
NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
}
控制臺(tái)輸出:
1
原因:
從控制臺(tái)輸出可以看出夕土,任務(wù)2和任務(wù)3沒有執(zhí)行,此時(shí)已經(jīng)死鎖了瘟判。
因?yàn)閐ispatch_sync是同步的怨绣,本身就會(huì)阻塞當(dāng)前線程,此刻阻塞了主線程拷获。而當(dāng)前block又在等待主線程執(zhí)行完畢篮撑,從而形成了主線程等待主線程,自己等自己的情況匆瓜,形成了死鎖赢笨。
解決方法:
- 改用異步dispatch_async執(zhí)行
NSLog(@"1"); // 任務(wù)1
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
控制臺(tái)輸出:
1
3
2
- 不在主線程中運(yùn)行,而是放在子線程中
NSLog(@"1"); // 任務(wù)1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
控制臺(tái)輸出:
1
2
3
注:如果block中是刷新UI的操作驮吱,則不能放在子線程中執(zhí)行茧妒,會(huì)crash
死鎖場(chǎng)景2: (同步串行隊(duì)列嵌套自己)
- (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
}
控制臺(tái)輸出:
1
2
原因:
從控制臺(tái)輸出結(jié)果來看,執(zhí)行到任務(wù)2后糠馆,就已經(jīng)死鎖了嘶伟。因?yàn)樵摾又袃蓚€(gè)GCD都是使用的同步方式,而且還是同一個(gè)串行隊(duì)列又碌,這就導(dǎo)致了和上一個(gè)例子一樣九昧,自己在等待自己的情況,形成了死鎖毕匀。
解決方法:
- 將第二個(gè)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
}
控制臺(tái)輸出:
1
2
4
5
3
然而铸鹰,將第一個(gè)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
}
控制臺(tái)輸出:
1
5
2
原因:
雖然第一個(gè)GCD是異步的皂岔,但是第二個(gè)GCD是同步的蹋笼,第二個(gè)GCD在等著第一個(gè)GCD結(jié)束,而第一個(gè)GCD的block又在等著第一個(gè)GCD結(jié)束躁垛,這樣就形成了死鎖剖毯。
注:對(duì)于以上將第二個(gè)GCD改為異步,第一個(gè)GCD為同步的場(chǎng)景教馆,不會(huì)造成死鎖逊谋,是因?yàn)榈诙€(gè)GCD為異步,它不用等待第一個(gè)GCD執(zhí)行完畢土铺,它和第一個(gè)GCD是沒有同步關(guān)系的胶滋。它是在第一個(gè)GCD執(zhí)行的同時(shí)并發(fā)執(zhí)行自己block的代碼。
- 將兩個(gè)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
}
控制臺(tái)輸出:
1
5
2
4
3
- 使用不同的串行隊(duì)列
- (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
}
控制臺(tái)輸出:
1
2
3
4
5
3. 死鎖場(chǎng)景: 信號(hào)量阻塞主線程
- (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í)行的線程是主線程悲敷,以上代碼就會(huì)出現(xiàn)死鎖究恤。
因?yàn)?code>dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了當(dāng)前線程,而且等待時(shí)間是DISPATCH_TIME_FOREVER
——永遠(yuǎn)等待后德,這樣它就永遠(yuǎn)的阻塞了當(dāng)前線程——主線程部宿。導(dǎo)致主線中的dispatch_semaphore_signal(semaphore)
沒有執(zhí)行,
而dispatch_semaphore_wait
一直在等待dispatch_semaphore_signal
改變信號(hào)量瓢湃,這樣就形成了死鎖窟赏。
解決方法:
應(yīng)該將信號(hào)量移到并行隊(duì)列中,如全局調(diào)度隊(duì)列箱季。以下場(chǎng)景涯穷,移到串行隊(duì)列也是可以的。但是串行隊(duì)列還是有可能死鎖的(如果執(zhí)行dispatch_semaphore_signal
方法還是在對(duì)應(yīng)串行隊(duì)列中的話藏雏,即之前提到的串行隊(duì)列嵌套串行隊(duì)列的場(chǎ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");
});
}