死鎖
1.定義
所謂死鎖,通常指的是兩個線程T1和T2都被卡住沃疮,并等待對方完成某些操作,T1等待T2完成梅肤,T2等待T1完成司蔬,于是大家都完成不了,就造成了死鎖(deadLock)
2.產(chǎn)生死鎖的條件
產(chǎn)生死鎖對的四個必要條件:
(1)互斥條件:一個資源每次只能被一個進程使用
(2)請求與保持條件:一個進程因請求資源而阻塞時姨蝴,對已獲得資源保持不放
(3)不剝奪條件:進程已得到的資源俊啼,在未使用完成之前,不能強行剝奪
(4)循環(huán)等待條件:若干進程之間形成一種頭尾相連的循環(huán)等待資源關(guān)系
這四個條件是死鎖的必要條件左医,只要系統(tǒng)發(fā)生死鎖授帕,這些條件必然成立,只要有一個不滿足浮梢,就不會死鎖
3.圖示
死鎖代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"這里死鎖了");
});
}
return 0;
}
//執(zhí)行這個dispatch_get_main_queue隊列的是主線程跛十,執(zhí)行了dispatch_sync函數(shù)后,將block添加到了main_queue中黔寇,同時調(diào)用這個dispatch_sync函數(shù)的線程被阻塞偶器,等待block完成,而執(zhí)行主線程隊列任務的線程正是主線程缝裤,此時他處于阻塞狀態(tài)屏轰,所以block永遠不會被執(zhí)行,因為主線程一直處于阻塞狀態(tài)憋飞,因此代碼運行之后霎苗,并非卡在了block無法返回,而是根本無法執(zhí)行到這個block
基本概念
1.同步&異步
1.1榛做、同步
同步執(zhí)行:比如這里的dispatch_sync,這個函數(shù)會把一block加入到指定的隊列唁盏,而且會一直等到執(zhí)行完block内狸,這個函數(shù)才會返回,因此在執(zhí)行完blcok之前厘擂,調(diào)用dispatch_sync的線程一直處于阻塞狀態(tài)
1.2昆淡、異步
異步執(zhí)行:dispatch_async,這個函數(shù)會把一個block添加到指定的隊列中刽严,但是和同步執(zhí)行不一樣的是昂灵,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回。
1.3舞萄、關(guān)于GCD函數(shù)的強調(diào)
dispatch_async 和dispatch_sync 他們的作用是將任務 (block) 添加進指定的隊列中眨补。并根據(jù)是否為sync決定調(diào)用該函數(shù)的線程是否需要阻塞
注意:這里調(diào)用該線程的函數(shù)并不執(zhí)行 參數(shù)中指定的任務(block塊),任務的執(zhí)行者是GCD分配給任務所在隊列的線程倒脓。
結(jié)論:調(diào)用dispatch_async和dispatch_sync線程撑螺,并不一定是任務(block塊)的執(zhí)行者。
2崎弃、串行&并發(fā)
2.1甘晤、串行隊列
串行隊列:比如dispatch_get_main_queue。隊列中所有任務饲做,一定按照FiFo(先進先出)執(zhí)行安皱。不僅如此,還可以保證在執(zhí)行某個任務時艇炎,在他前面進入隊列的所有任務肯定執(zhí)行完了對于每一個不同的串行隊列,系統(tǒng)都會為這個隊列建立唯一的線程來執(zhí)行代碼
2.2腾窝、并發(fā)隊列
比如dispatch_get_global_queue缀踪。這個隊列中的任務也是按照先進先出的順序執(zhí)行,但是他們執(zhí)行結(jié)束的時間是不確定的虹脯,取決于每個任務的耗時驴娃,并發(fā)隊列中的任務:GCD會動態(tài)分配多條線程來執(zhí)行,具體幾條線程取決于當前內(nèi)存使用狀況循集,線程池中線程數(shù)等因素唇敞。
3、總結(jié)
3.1咒彤、異步執(zhí)行block肯定不會發(fā)生死鎖疆柔,回顧一下導致死鎖的原因,是因為主線程在執(zhí)行dispatch_sync镶柱,這是一個同步方法旷档,block執(zhí)行完成之前都不會返回。而async是異步的執(zhí)行歇拆,是立刻返回的鞋屈,因此不會阻塞主線程范咨,雙向的阻塞不成立,只是主線程處理block時候阻塞厂庇,這不會引起死鎖
3.2渠啊、同步的向并發(fā)隊列添加block不會導致死鎖
原因:由于之前在串行隊列中添加了block,block一直要等待前面的任務處理完成才會執(zhí)行权旷,從而造成了死鎖替蛉。如果采用同步的向并發(fā)隊列中添加block,首先需要明確一點炼杖,就是同步方法是不會再開線程的灭返,也就是說當前的調(diào)用線程會立即去執(zhí)行block,直到block執(zhí)行完成后才會繼續(xù)向下執(zhí)行坤邪。因為是并發(fā)隊列熙含,隊列中的下一個任務執(zhí)行不需要等待上一個任務的完成,所以艇纺,即使添加到當前調(diào)用任務的隊列也不會造成死鎖怎静,當前線程會立即執(zhí)行新添加的任務,然后返回黔衡,并繼續(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ā)隊列中:同一條線程在上一個任務沒有完成的情況下,可以去執(zhí)行下一個任務盟劫。因為并發(fā)隊列中下一個任務的執(zhí)行不需要上一個任務執(zhí)行完成夜牡。
3.2、如果同步向另一個串行隊列添加方法侣签,并不一定會造成死鎖塘装。
dispatch_queue_t queue = dispatch_queue_create("simple",nil);
dispatch_sync(queue,^(void){
nslog(@"哈哈哈哈")
});
因為:simple這個隊列的執(zhí)行線程是主線程,同步方法不會開辟新線程影所,但這個是將任務添加到了simple這個隊列中蹦肴,所以主線程會立即來執(zhí)行這個隊列中的任務,執(zhí)行完后就會返回猴娩,因此主線程不會繼續(xù)被阻塞阴幌,所以不會死鎖
造成死鎖的唯一原因:
在某一串行隊列中,同步的向這個串行隊列添加block
因為隊列是可以嵌套的卷中,比如在A隊列(串行)添加一個任務a矛双,在a這個任務中像B隊列(串行)添加任務b,在b這個任務中又向A隊列添加任務蟆豫,這間接滿足了“在某一個串行隊列中背零,同步的向這個隊列添加block”。但是我們好像每一次都沒有直接向相同的隊列添加block
所以:判斷是否發(fā)生死鎖的最好方法就是看有沒有在串行隊列(包括主隊列)中向這個隊列添加任務
我們使用同步的方法編程无埃,往往是要求保證人物之間的執(zhí)行順序是完全正確的徙瓶。且不說GCD提供了很多強大的功能來滿足這個需求毛雇,向串行隊列在同步的添加任務本身就是不合理的,畢竟隊列已經(jīng)是串行了侦镇,直接異步添加就可以了
dispatch_queue_t queue = dispatch_queue_create("test.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_apply(3, queue, ^(size_t i) {
NSLog(@"apply loop: %zu", i);
//再來一個dispatch_apply灵疮!死鎖!
dispatch_apply(3, queue, ^(size_t j) {
NSLog(@"apply loop inside %zu", j);
});
});
dsipatch_apply嵌套使用也會死鎖