iOS 同步,異步,串行隊列,并發(fā)隊列,全局隊列,主隊列以及死鎖原因分析

GCD中涉及到兩個十分重要的概念, 就是任務(wù)和隊列

  • 任務(wù)(Task): 你需要執(zhí)行的操作
  • 隊列(Queue): 存放任務(wù)的容器

GCD中兩個重要的函數(shù), 一個同步執(zhí)行, 一個異步執(zhí)行

dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)
dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)

這兩個函數(shù)中均需要填入兩個參數(shù), 一個是隊列, 一個是任務(wù), 任務(wù)就是封裝在block代碼塊中的. 所以, 我們在使用以上兩個函數(shù)時, 只需要創(chuàng)建隊列,將任務(wù)(block)添加進指定的隊列中。并根據(jù)是否為sync決定調(diào)用該函數(shù)的線程是否需要阻塞辣苏。

注意:

這里調(diào)用該函數(shù)的線程并不執(zhí)行參數(shù)中指定的任務(wù)(block塊)檐晕,任務(wù)的執(zhí)行者是GCD分配給任務(wù)所在隊列的線程。調(diào)用dispatch_sync和dispatch_async的線程,并不一定是任務(wù)(block塊)的執(zhí)行者雏胃。

同步執(zhí)行和異步執(zhí)行有什么區(qū)別呢?

同步執(zhí)行:比如這里的dispatch_sync婚度,這個函數(shù)會把一個block加入到指定的隊列中,而且會一直等到執(zhí)行完blcok裹芝,這個函數(shù)才返回。因此在block執(zhí)行完之前娜汁,調(diào)用dispatch_sync方法的線程是阻塞的嫂易。

異步執(zhí)行:一般使用dispatch_async,這個函數(shù)也會把一個block加入到指定的隊列中掐禁,但是和同步執(zhí)行不同的是怜械,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了。

另外, 還有一點需要明確的是

  • 同步執(zhí)行沒有開啟新線程的能力, 所有的任務(wù)都只能在當前線程執(zhí)行
  • 異步執(zhí)行有開啟新線程的能力, 但是, 有開啟新線程的能力, 也不一定會利用這種能力, 也就是說, 異步執(zhí)行是否開啟新線程, 需要具體問題具體分析

我們再來看看串行隊列和并發(fā)隊列

無論任何隊列, 其實都遵循FIFO(first in first out, 先進先出原則)

串行隊列可以保證在執(zhí)行某個任務(wù)時穆桂,在它前面進入隊列的所有任務(wù)肯定執(zhí)行完了宫盔。對于每一個不同的串行隊列,系統(tǒng)會為這個隊列建立唯一的線程來執(zhí)行代碼享完。
但是并發(fā)隊列它們的執(zhí)行結(jié)束時間是不確定的灼芭,取決于每個任務(wù)的耗時。并發(fā)隊列中的任務(wù):GCD會動態(tài)分配多條線程來執(zhí)行般又。具體幾條線程取決于當前內(nèi)存使用狀況彼绷,線程池中線程數(shù)等因素。

清楚了以上概念,下面我們來根據(jù)他們的互相組合會發(fā)生什么,讓我們拭目以待

1.同步執(zhí)行+ 串行隊列
- (void)syncSerial{
    /* 1.創(chuàng)建一個串行隊列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
    /* 2.將任務(wù)放在隊列中 */
    dispatch_sync(serialQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

download1--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download2--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download3--------<NSThread: 0x600003536cc0>{number = 1, name = main}

可以看出的是: 同步執(zhí)行 + 串行隊列沒有開啟新的線程, 所有的操作, 都是在當前線程執(zhí)行.如果是在子線程中執(zhí)行同步串行隊列的操作, 當前的線程就是子線程,如下.

download1--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600002e35b80>{number = 3, name = (null)}
執(zhí)行完任務(wù)三
2.同步執(zhí)行+ 并發(fā)隊列
- (void)syncConcurrent{
    /* 1.創(chuàng)建一個并發(fā)隊列 */
    dispatch_queue_t cuncurrentQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_CONCURRENT);
    /* 2.將不同的任務(wù)添加到隊列中 */
    dispatch_sync(cuncurrentQueue, ^{
         NSLog(@"download1--------%@",[NSThread currentThread]);
    });
     NSLog(@"執(zhí)行完任務(wù)一");
    dispatch_sync(cuncurrentQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
     NSLog(@"執(zhí)行完任務(wù)二");
    dispatch_sync(cuncurrentQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
     NSLog(@"執(zhí)行完任務(wù)三");
}

執(zhí)行情況:

download1--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600000530640>{number = 1, name = main}
執(zhí)行完任務(wù)三

可以看出的是:三個任務(wù)都在主線程中執(zhí)行, 并沒有開啟新的線程. 但是, 是不是所有的同步執(zhí)行的操作都在主線程中執(zhí)行呢? 當然不是. 看下面的代碼

- (void)syncConcurrentOnBackgroundThread {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self syncConcurrent];
    });
}

同理:我把同步執(zhí)行+ 并發(fā)隊列這個操作也放到子線程去執(zhí)行, 那么執(zhí)行的線程就是子線程

download1--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)一
download2--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)二
download3--------<NSThread: 0x600002e38d00>{number = 4, name = (null)}
執(zhí)行完任務(wù)三
3.異步執(zhí)行+ 串行隊列
- (void)asyncSerial{
    /* 1.創(chuàng)建一個串行隊列 */
   dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
    /* 2.將不同的任務(wù)添加到隊列中 */
    dispatch_async(serialQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

download1--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}
download2--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}
download3--------<NSThread: 0x60000202cb00>{number = 3, name = (null)}

可以看出的是: 異步執(zhí)行 + 串行隊列也開啟了新的線程, 但是不管任務(wù)有多少個, 異步執(zhí)行 + 同一條串行隊列只開啟一條新的線程, 任務(wù)的執(zhí)行順序也是按照隊列中的順序執(zhí)行的, 因為同一條線程中, 必須等到前一個任務(wù)執(zhí)行完畢后, 才能執(zhí)行下一個任務(wù).

4.異步執(zhí)行+ 并發(fā)隊列
- (void)asyncConcurrent{
    /* 1.創(chuàng)建一個并發(fā)隊列 */
  dispatch_queue_t concurrentQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_CONCURRENT);
    /* 2.將任務(wù)放到隊列中, 下面的代碼將三個任務(wù)放到隊列中 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"download2-------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"download3-------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

執(zhí)行完任務(wù)一
download3-------<NSThread: 0x600003d420c0>{number = 5, name = (null)}
download2-------<NSThread: 0x600003d42000>{number = 4, name = (null)}
download1-------<NSThread: 0x600003d5b840>{number = 3, name = (null)}

可以看出的是:開啟了不同的線程, 且\color{red}{任務(wù)完成的順序也是隨機的},但是線程也不會無限開啟,比如說你有100個任務(wù),會開啟100條線程嗎?\color{red}{答案是果斷不會}

除了以上這幾類組合,還有兩個概念,一個是全局并發(fā)隊列主隊列

全局并發(fā)隊列:就是我們常說的全局隊列,它是一個并發(fā)隊列, 它是系統(tǒng)為我們創(chuàng)建好的一個全局并發(fā)隊列, 所以, 有時候, 我們不需要自己創(chuàng)建一個并發(fā)隊列, 直接用系統(tǒng)為我們提供的全局隊列就可以了,所以全局隊列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊列是一樣的
比較特殊的是主隊列
主隊列:系統(tǒng)會把主隊列中的任務(wù)放在主線程中執(zhí)行

5.異步執(zhí)行+ 主隊列
- (void)asyncMainQueue{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download1------%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download2------%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download3------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

download1------<NSThread: 0x600001906780>{number = 1, name = main}
download2------<NSThread: 0x600001906780>{number = 1, name = main}
download3------<NSThread: 0x600001906780>{number = 1, name = main}

需要注意的是:異步執(zhí)行雖然有開啟新線程的能力, 但是\color{red}{異步執(zhí)行 + 主隊列并不會開啟新的線程,任務(wù)都是在主線程中執(zhí)行的}

6.同步執(zhí)行+ 主隊列
- (void)syncMainQueue{
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download2------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download3------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況如下圖:

死鎖.png

直接奔潰,

關(guān)于死鎖
1.概念

所謂死鎖茴迁,通常指有兩個線程T1和T2都卡住了寄悯,并等待對方完成某些操作。T1不能完成是因為它在等待T2完成堕义。但T2也不能完成猜旬,因為它在等待T1完成。于是大家都完不成,就導致了死鎖(DeadLock)

2.產(chǎn)生死鎖的四個必要條件:

(1) 互斥條件:一個資源每次只能被一個進程使用洒擦。
(2) 請求與保持條件:一個進程因請求資源而阻塞時椿争,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源熟嫩,在末使用完之前秦踪,不能強行剝奪。
(4) 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系掸茅。

這四個條件是死鎖的必要條件椅邓,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立昧狮,而只要上述條件之一不滿足景馁,就不會發(fā)生死鎖。

首先來看一份經(jīng)典的死鎖代碼:

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í)行完成,而執(zhí)行主線程隊列任務(wù)的線程正是主線程茬底,此時他處于阻塞狀態(tài)沪悲,所以block永遠不會被執(zhí)行,因此主線程一直處于阻塞狀態(tài)阱表。因此這段代碼運行后殿如,并非卡在block中無法返回,而是根本無法執(zhí)行到這個block最爬。

同理前面死鎖的代碼,可以這樣理解:
執(zhí)行syncMainQueue這個方法是在主線程中執(zhí)行的, 你可以把它看做一個任務(wù)A, 這個任務(wù)A也是在主隊列中的,那么代碼執(zhí)行到第179行的時候, 啟動了任務(wù)B, 把任務(wù)B放進了主隊列中, 由于是同步執(zhí)行, 所以, 必須等待任務(wù)B執(zhí)行完了之后才能繼續(xù)向下執(zhí)行, 但是主線程有任務(wù)A, 所以任務(wù)B無法放到主線程中去執(zhí)行,任務(wù)B等待任務(wù)A執(zhí)行, 任務(wù)A等待任務(wù)B執(zhí)行, 這樣就造成了死鎖.
如圖


同步執(zhí)行+ 主隊列 造成死鎖的原因.png

但是,如果將同步執(zhí)行+ 主隊列的操作放到子線程中執(zhí)行, 就不會造成死鎖

- (void)syncMainOnBackgroundThread{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self syncMainQueue];
    });
}

執(zhí)行情況:

download1------<NSThread: 0x600001c0df40>{number = 1, name = main}
download2------<NSThread: 0x600001c0df40>{number = 1, name = main}
download3------<NSThread: 0x600001c0df40>{number = 1, name = main}

那為什么同步執(zhí)行 + 串行隊列不會造成死鎖呢?

- (void)syncSerial{
    /* 1.創(chuàng)建一個串行隊列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("download.zavier.com", DISPATCH_QUEUE_SERIAL);
    /* 2.將任務(wù)放在隊列中 */
    dispatch_sync(serialQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

download1--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download2--------<NSThread: 0x600003536cc0>{number = 1, name = main}
download3--------<NSThread: 0x600003536cc0>{number = 1, name = main}

原因是:download.zavier.com這個隊列的執(zhí)行線程是主線程,同步執(zhí)行不會開辟新線程,但這時候我們將任務(wù)添加到了download.zavier.com這個隊列中,所以主線程會來立即執(zhí)行這個隊列中的任務(wù)涉馁,執(zhí)行完成后就會返回,主線程不會繼續(xù)被阻塞爱致。所以不會死鎖


2868984-3d697efcf03fcc3c.png

事實上,導致死鎖的原因一定是:
在某一個串行隊列中烤送,同步的向這個串行隊列添加block。

說了這么多, 可能又有點暈, 其實這些應(yīng)該在實際開發(fā)中慢慢體會, 碰到的情況多了, 自然而然就明白了. 現(xiàn)在, 我們只要記住下面的表格就可以了

各種隊列的執(zhí)行效果圖:
各種隊形的執(zhí)行效果圖.png

參考文獻:
iOS中GCD產(chǎn)生死鎖原因分析及解決方案
同步,異步,串行隊列,并發(fā)隊列,全局隊列,主隊列等概念的總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糠悯,一起剝皮案震驚了整個濱河市帮坚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互艾,老刑警劉巖试和,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纫普,居然都是意外死亡阅悍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节视,“玉大人晦墙,你說我怎么就攤上這事‰惹眩” “怎么了晌畅?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寡痰。 經(jīng)常有香客問我抗楔,道長,這世上最難降的妖魔是什么拦坠? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任连躏,我火速辦了婚禮,結(jié)果婚禮上贞滨,老公的妹妹穿的比我還像新娘入热。我一直安慰自己,他們只是感情好晓铆,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布勺良。 她就那樣靜靜地躺著,像睡著了一般骄噪。 火紅的嫁衣襯著肌膚如雪尚困。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天链蕊,我揣著相機與錄音事甜,去河邊找鬼。 笑死滔韵,一個胖子當著我的面吹牛逻谦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陪蜻,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼邦马,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了囱皿?” 一聲冷哼從身側(cè)響起勇婴,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘱腥,沒想到半個月后耕渴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡齿兔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年橱脸,在試婚紗的時候發(fā)現(xiàn)自己被綠了础米。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡添诉,死狀恐怖屁桑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栏赴,我是刑警寧澤蘑斧,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站须眷,受9級特大地震影響竖瘾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜花颗,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一捕传、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扩劝,春花似錦庸论、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至条霜,卻和暖如春催什,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宰睡。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留气筋,地道東北人拆内。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像宠默,于是被迫代替她去往敵國和親麸恍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容