GCD死鎖探討
先看一個簡單程序:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(@"開始運行%@",[NSThread currentThread]);
[self longTimeTask];
NSLog(@"運行完畢%@",[NSThread currentThread]);
}
- (void)longTimeTask{
NSLog(@"任務開始執(zhí)行%@",[NSThread currentThread]);
sleep(10);
NSLog(@"任務執(zhí)行完畢%@",[NSThread currentThread]);
}
/*輸出結果:
2017-10-12 09:46:45.176989+0800 GCDLearnAdvanced[910:54133] 開始運行<NSThread: 0x608000065d40>{number = 1, name = main}
2017-10-12 09:46:45.177220+0800 GCDLearnAdvanced[910:54133] 任務開始執(zhí)行<NSThread: 0x608000065d40>{number = 1, name = main}
2017-10-12 09:46:55.178354+0800 GCDLearnAdvanced[910:54133] 任務執(zhí)行完畢<NSThread: 0x608000065d40>{number = 1, name = main}
2017-10-12 09:46:55.178486+0800 GCDLearnAdvanced[910:54133] 運行完畢<NSThread: 0x608000065d40>{number = 1, name = main}
*/
可以看出,viewDidLoad執(zhí)行了第一句打印后竞阐,會跳入longTimeTask這個函數(shù)執(zhí)行,直到longTimeTask執(zhí)行完畢,才會返回到viewDidLoad中繼續(xù)執(zhí)行下一句闸度。這里重點是程序是順序執(zhí)行的,只有執(zhí)行完上一個后才會繼續(xù)往下執(zhí)行蚜印。
看一個死鎖案例:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(@"開始運行%@",[NSThread currentThread]);
dispatch_queue_t serialMQ = dispatch_get_main_queue();
dispatch_sync(serialMQ, ^{
NSLog(@"task %@",[NSThread currentThread]);
});
NSLog(@"運行完畢%@",[NSThread currentThread]);
}
/*輸出結果:
2017-10-12 09:59:13.885921+0800 GCDLearnAdvanced[1007:72128] 開始運行<NSThread: 0x608000075540>{number = 1, name = main}
(lldb)
*/
可以看到莺禁,只打印“開始運行”就再也沒有響應了,已經(jīng)造成了死鎖窄赋。
分析程序理論執(zhí)行:viewDidLoad開始執(zhí)行哟冬,首先打印“開始運行”,接著執(zhí)行dispatch_sync()函數(shù)寝凌,dispatch_sync把一個block添加到主隊列中柒傻,等待dispatch_sync()執(zhí)行完畢后 viewDidLoad繼續(xù)往下執(zhí)行--打印“運行完畢”。而實際卻只打印了“開始運行”较木,那么肯定是dispatch_sync()函數(shù)出現(xiàn)了問題红符。那么dispatch_sync()出了什么問題呢,繼續(xù)分析
dispatch_sync() API解釋:Submits a block object for execution on a dispatch queue and waits until that block completes.
也就是這個block不執(zhí)行完的話這個函數(shù)永遠沒有返回伐债,也就是永遠沒有執(zhí)行完畢预侯。
主隊列dispatch_get_main_queue() ,是特殊的串行隊列峰锁,任務會先進先出的一個一個在主線程執(zhí)行萎馅。程序中的所有內(nèi)容都在主隊列上運行,除了顯式地發(fā)送到另一個隊列的代碼除外虹蒋。在主隊列的代碼總是運行在主線程上
深入分析死鎖原因糜芳,在此案例中,viewDidLoad先被加入主隊列魄衅,隨后dispatch_sync()再把一個block加入主隊列峭竣,遵循先進先出原理,viewDidLoad相當于隊列的隊頭晃虫,block要想執(zhí)行必須等待viewDidLoad執(zhí)行完畢后才會執(zhí)行皆撩。但是對于viewDidLoad函數(shù)來說 dispatch_sync函數(shù)中block不執(zhí)行完這個函數(shù)就相當于永遠沒有執(zhí)行完畢,viewDidLoad函數(shù)沒辦法繼續(xù)往下執(zhí)行哲银。這樣就造成了相互等待扛吞,viewDidLoad函數(shù)等待dispatch_sync函數(shù)執(zhí)行完畢呻惕,它才能繼續(xù)往下執(zhí)行;dispatch_sync函數(shù)等待viewDidLoad執(zhí)行完畢滥比,這樣它的block才能執(zhí)行亚脆,最終導致死鎖。
再分析一個案例:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(@"開始運行%@",[NSThread currentThread]);
dispatch_queue_t serialQ = dispatch_queue_create("customSerial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQ, ^{
NSLog(@"任務 A %@",[NSThread currentThread]);
});
NSLog(@"運行完畢%@",[NSThread currentThread]);
}
/*輸出結果:
2017-10-12 11:08:16.259113+0800 GCDLearnAdvanced[1289:165769] 開始運行<NSThread: 0x60c000065040>{number = 1, name = main}
2017-10-12 11:08:16.259387+0800 GCDLearnAdvanced[1289:165769] 任務 A <NSThread: 0x60c000065040>{number = 1, name = main}
2017-10-12 11:08:16.259510+0800 GCDLearnAdvanced[1289:165769] 運行完畢<NSThread: 0x60c000065040>{number = 1, name = main}
*/
可以看到程序正常執(zhí)行守呜,沒有造成死鎖型酥。為什么呢?根據(jù)主隊列同步死鎖原因查乒,我們很容易就能分析出弥喉,dispatch_sync函數(shù)中的block任務是在自定義串行隊列中分配執(zhí)行的,所以不會死鎖玛迄。
viewDidLoad執(zhí)行到dispatch_sync函數(shù)等待其運行完畢時由境,dispatch_sync()向“customSerial”串行隊列添加了一個同步block,而在“customSerial”串行隊列中蓖议,block的執(zhí)行不需要等待viewDidLoad的執(zhí)行虏杰,因為它們并沒有在同一個隊列中。這樣block中任務在主線程執(zhí)行完畢后函數(shù)返回勒虾,viewDidLoad繼續(xù)正常往下執(zhí)行(造成死鎖需要互相等待 死鎖_百度百科)
再分析最后一個案例:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(@"開始運行%@",[NSThread currentThread]);
dispatch_queue_t serialQ = dispatch_queue_create("customSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ, ^{
NSLog(@"任務 A Begin %@",[NSThread currentThread]);
dispatch_sync(serialQ, ^{
NSLog(@"任務B %@",[NSThread currentThread]);
});
NSLog(@"任務 A End %@",[NSThread currentThread]);
});
NSLog(@"運行完畢%@",[NSThread currentThread]);
}
/*輸出結果:
2017-10-12 11:16:05.993607+0800 GCDLearnAdvanced[1337:174414] 開始運行<NSThread: 0x60000007dbc0>{number = 1, name = main}
2017-10-12 11:16:05.993888+0800 GCDLearnAdvanced[1337:174414] 運行完畢<NSThread: 0x60000007dbc0>{number = 1, name = main}
2017-10-12 11:16:05.993914+0800 GCDLearnAdvanced[1337:174445] 任務 A Begin <NSThread: 0x60c000469900>{number = 3, name = (null)}
(lldb)
*/
可以看到程序執(zhí)行完“任務A Begin”就死鎖了纺阔。
分析函數(shù),在viewDidLoad執(zhí)行一個異步+串行任務A修然,任務A執(zhí)行一個同步+串行任務B笛钝。我們可以看到程序是在dispatch_sync這個地方死鎖了。為什么這個同步+自定義串行隊列會死鎖呢愕宋,上面案例就沒有死鎖玻靡?看明白的小伙伴應該就能發(fā)現(xiàn)死鎖的特點了,在同一串行隊列添加同步任務就會造成死鎖中贝。
分析這個案例囤捻,在任務A中執(zhí)行dispatch_sync函數(shù)時,dispatch_sync函數(shù)向串行隊列“customSerial”添加了一個block任務B邻寿,串行隊列“customSerial”一次執(zhí)行一個任務的蝎土,dispatch_sync()中的block任務B必須等到前一個任務執(zhí)行完畢,可“customSerial”正在執(zhí)行的任務就是被dispatch_sync()阻塞了的那個绣否,于是又發(fā)生了死鎖瘟则。
最終我們得出的結論是 在同一個串行隊列中添加同步任務會導致死鎖。
相關參考:
https://stackoverflow.com/questions/41312006/sync-call-serial-queue-created-in-main-is-ok-while-sync-call-main-queue-in-main
http://www.reibang.com/p/bbabef8aa1fe