同步,異步,串行隊(duì)列,并發(fā)隊(duì)列,全局隊(duì)列,主隊(duì)列等概念的總結(jié)

本人有若干成套學(xué)習(xí)視頻, 可試看! 可試看! 可試看, 重要的事情說(shuō)三遍 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

在GCD函數(shù)中, 我們常常碰到同步,異步,串行隊(duì)列,并發(fā)隊(duì)列,全局隊(duì)列,主隊(duì)列等概念,而這些概念又常常組合在一起, 十分頭疼, 這篇文章就來(lái)梳理一下這些煩人的概念.

不想看長(zhǎng)篇大論的, 直接看文章末尾的表格即可!

在此之前, GCD中還涉及到兩個(gè)十分重要的概念, 就是任務(wù)隊(duì)列

  • 任務(wù)(Task): 你需要執(zhí)行的操作
  • 隊(duì)列(Queue): 存放任務(wù)的容器
GCD中兩個(gè)重要的函數(shù), 一個(gè)同步執(zhí)行, 一個(gè)異步執(zhí)行
dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)
dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)

這個(gè)函數(shù)中需要填入兩個(gè)參數(shù), 一個(gè)是隊(duì)列, 一個(gè)是任務(wù), 任務(wù)就是封裝在block代碼塊中的. 所以, 我們?cè)谑褂靡陨蟽蓚€(gè)函數(shù)時(shí), 只需要?jiǎng)?chuàng)建隊(duì)列, 以及把自己需要執(zhí)行的代碼封裝在block中就可以了

CGD中還給我們提供了兩個(gè)類似的函數(shù), 請(qǐng)看

dispatch_sync_f(dispatch_queue_t queue,
    void *_Nullable context,
    dispatch_function_t work);

dispatch_async_f(dispatch_queue_t queue,
    void *_Nullable context,
    dispatch_function_t work);

這兩個(gè)函數(shù)就沒(méi)有之前那兩個(gè)簡(jiǎn)單, 因?yàn)檫@兩個(gè)函數(shù)不是將任務(wù)封裝在block代碼塊中, 而是封裝在函數(shù)里

如下面的代碼所示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async_f(dispatch_get_global_queue(0, 0), nil, task);
}

void task (void *param) {
    NSLog(@"downloadTask-------%@",[NSThread currentThread]);
}

dispatch_async_f(_, _, _);這個(gè)函數(shù)的三個(gè)參數(shù)我分別傳入

  • 一個(gè)全局并發(fā)隊(duì)列
  • 參數(shù)我傳空
  • 定義一個(gè)返回值為空, 參數(shù)為void*類型的函數(shù), 然后把函數(shù)名作為參數(shù)傳進(jìn)去

在我定義的這個(gè)返回值為空, 參數(shù)為void*類型的函數(shù)中, 我把我需要操作的任務(wù)封裝在里面, 其實(shí)里面的代碼就跟之前block代碼塊里的代碼一模一樣. 也能起到同樣的效果

那么, 同步執(zhí)行和異步執(zhí)行有什么區(qū)別呢? 我們先來(lái)看看官方文檔的解釋

When a work item is executed synchronously with the** sync** method, the program waits until execution finishes before the method call returns

When a work item is executed asynchronously with the async method, the method call returns immediately

也就是說(shuō)

假如我有A,B,C三個(gè)任務(wù),

  • 如果這三個(gè)任務(wù)都是同步執(zhí)行, 程序?qū)⒌却鼳 執(zhí)行完畢之后, 再執(zhí)行B, 再執(zhí)行C

  • 如果這三個(gè)任務(wù)都是是異步執(zhí)行, 程序直接跳過(guò)A,B,C,執(zhí)行后面的代碼, 執(zhí)行完畢之后, 再來(lái)執(zhí)行A,B,C中的任務(wù)

另外, 還有一點(diǎn)需要明確的是
  • 同步執(zhí)行沒(méi)有開(kāi)啟新線程的能力, 所有的任務(wù)都只能在當(dāng)前線程執(zhí)行
  • 異步執(zhí)行有開(kāi)啟新線程的能力, 但是, 有開(kāi)啟新線程的能力, 也不一定會(huì)利用這種能力, 也就是說(shuō), 異步執(zhí)行是否開(kāi)啟新線程, 需要具體問(wèn)題具體分析
我們?cè)賮?lái)看一下串行隊(duì)列和并發(fā)隊(duì)列

無(wú)論任何隊(duì)列, 其實(shí)都遵循FIFO(first in first out, 先進(jìn)先出原則),
但是:

  • 并發(fā)隊(duì)列中的任務(wù)會(huì)放到不同的線程中去執(zhí)行.
  • 串行隊(duì)列中的任務(wù)只會(huì)放到同一線程中去執(zhí)行.

如下圖所示

并發(fā)隊(duì)列
串行隊(duì)列

那么同步執(zhí)行,異步執(zhí)行,并發(fā)隊(duì)列,串行隊(duì)列互相組合又會(huì)發(fā)生什么樣的情況呢? 這個(gè)時(shí)候, 就有四種情況需要分析了

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

執(zhí)行情況


異步執(zhí)行 + 并發(fā)隊(duì)列 執(zhí)行情況

可以看出, 開(kāi)啟了不同的線程, 任務(wù)完成的順序也是隨機(jī)的

但是不同的任務(wù)都開(kāi)啟一個(gè)獨(dú)立的線程, 那我有100個(gè)任務(wù),會(huì)開(kāi)啟100條線程嗎? 答案是果斷不會(huì), 如下圖所示

線程開(kāi)啟數(shù)量會(huì)無(wú)限大嗎?
可以看出的是, 任務(wù),10,11,12利用了之前的線程, 所以線程是不會(huì)無(wú)限開(kāi)啟的.
  • 情況2 : 異步執(zhí)行 + 串行隊(duì)列
    /****************** -------- 異步執(zhí)行 + 串行隊(duì)列 -------- ******************/
- (void)asyncSerial {
    
     /* 1. 創(chuàng)建一個(gè)串行隊(duì)列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);
    
     /* 2. 將不同的任務(wù)添加到隊(duì)列中 */
    dispatch_async(serialQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    
    dispatch_async(serialQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    
    dispatch_async(serialQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

異步執(zhí)行 + 串行隊(duì)列 執(zhí)行情況

可以看出的是: 異步執(zhí)行 + 串行隊(duì)列也開(kāi)啟了新的線程, 但是不管任務(wù)有多少個(gè), 異步執(zhí)行 + 同一條串行隊(duì)列只開(kāi)啟一條新的線程, 任務(wù)的執(zhí)行順序也是按照隊(duì)列中的順序執(zhí)行的, 因?yàn)橥粭l線程中, 必須等到前一個(gè)任務(wù)執(zhí)行完畢后, 才能執(zhí)行下一個(gè)任務(wù).

  • 情況3 : 同步執(zhí)行+ 并發(fā)隊(duì)列
    /****************** -------- 同步執(zhí)行 + 并發(fā)隊(duì)列 -------- ******************/
- (void)syncConcurrent {
    
     /* 1. 創(chuàng)建一條并發(fā)隊(duì)列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_CONCURRENT);
    
     /* 2. 把任務(wù)放到隊(duì)列中 */
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    
}

執(zhí)行情況:

同步執(zhí)行+ 并發(fā)隊(duì)列 執(zhí)行情況

三個(gè)任務(wù)都在主線程中執(zhí)行, 并沒(méi)有開(kāi)啟新的線程. 但是, 是不是所有的同步執(zhí)行的操作都在主線程中執(zhí)行呢? 當(dāng)然不是. 看下面的代碼

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

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

同步執(zhí)行+ 并發(fā)隊(duì)列 在子線程中執(zhí)行

所以說(shuō), 同步執(zhí)行+ 并發(fā)隊(duì)列, 并不會(huì)開(kāi)啟新的線程, 即使是并發(fā)隊(duì)列, 也然并卵

  • 情況4 : 同步執(zhí)行+ 串行隊(duì)列
    /****************** -------- 同步操作 + 串行隊(duì)列 -------- ******************/
- (void)syncSerial {
     /* 1. 創(chuàng)建串行隊(duì)列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);
    
     /* 2. 將任務(wù)放到隊(duì)列中 */
    dispatch_sync(serialQueue, ^{
        NSLog(@"download1--------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"download2--------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"download3--------%@",[NSThread currentThread]);
    });
}

執(zhí)行情況:

同步執(zhí)行 + 串行隊(duì)列 執(zhí)行情況

如果是在子線程中執(zhí)行同步串行隊(duì)列的操作, 當(dāng)前的線程就是子線程

同步執(zhí)行 + 串行隊(duì)列 子線程執(zhí)行情況
總之, 需要記住的就是, 同步執(zhí)行并沒(méi)有開(kāi)啟子線程的能力, 所有的操作, 都是在當(dāng)前線程執(zhí)行.
故事到這里就結(jié)束了嗎? 并沒(méi)有

還有一個(gè)操蛋的全局并發(fā)隊(duì)列主隊(duì)列, 這兩個(gè)又是什么鬼呢?

全局并發(fā)隊(duì)列就是我們常說(shuō)的全局隊(duì)列

首先, 它是一個(gè)并發(fā)隊(duì)列, 他是系統(tǒng)為我們創(chuàng)建好的一個(gè)全局的并發(fā)隊(duì)列, 所以, 有時(shí)候, 我們不需要自己創(chuàng)建一個(gè)并發(fā)隊(duì)列, 直接用系統(tǒng)為我們提供的全局隊(duì)列就可以了,所以全局隊(duì)列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊(duì)列是一樣的

比較特殊的是主隊(duì)列
系統(tǒng)會(huì)把主隊(duì)列中的任務(wù)放在主線程中執(zhí)行

  • 情況5 : 異步執(zhí)行+ 主隊(duì)列
    /****************** -------- 異步執(zhí)行 + 主隊(duì)列 -------- ******************/
- (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í)行情況:

異步執(zhí)行+ 主隊(duì)列

異步執(zhí)行雖然有開(kāi)啟新線程的能力, 但是異步執(zhí)行 + 主隊(duì)列并不會(huì)開(kāi)啟新的線程, 任務(wù)都是在主線程中執(zhí)行的

  • 情況6 : 同步執(zhí)行+ 主隊(duì)列
    /****************** -------- 同步執(zhí)行 + 主隊(duì)列 -------- ******************/
- (void)syncMainQueue {
    
    NSLog(@"------start-------");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    
    NSLog(@"-------end---------");
    
}

執(zhí)行情況:

同步執(zhí)行+ 主隊(duì)列 執(zhí)行情況

直接崩潰了, 以前這種情況是會(huì)發(fā)生死鎖的, 不知道是不是因?yàn)槭荴Code8.2的原因, 現(xiàn)在直接報(bào)錯(cuò). 那么, 為什么會(huì)發(fā)生這種情況呢?

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

如圖所示:

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

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

同步執(zhí)行+ 主隊(duì)列 在子線程中執(zhí)行情況

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

同步執(zhí)行是不會(huì)開(kāi)啟新的線程的, 如果當(dāng)前線程是主線程, 則任務(wù)在主線程中執(zhí)行. 如下圖所示

同步執(zhí)行+ 串行隊(duì)列 不會(huì)造成死鎖

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

各種隊(duì)列的執(zhí)行效果圖:
各種隊(duì)列的執(zhí)行效果圖

源碼奉上, 大家自己寫(xiě)了試試, 測(cè)試一下, 然后應(yīng)該很容易就理解了
源碼

有任何問(wèn)題, 歡迎留言交流討論, 謝謝!

PS. 本人有若干成套學(xué)習(xí)視頻, 包含Java, 數(shù)據(jù)結(jié)構(gòu)與算法, iOS, 安卓, python, flutter等等, 如有需要, 聯(lián)系微信tsaievan.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末台夺,一起剝皮案震驚了整個(gè)濱河市剂陡,隨后出現(xiàn)的幾起案子趋观,更是在濱河造成了極大的恐慌诽偷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件用押,死亡現(xiàn)場(chǎng)離奇詭異肢簿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蜻拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門池充,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人缎讼,你說(shuō)我怎么就攤上這事纵菌。” “怎么了休涤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵咱圆,是天一觀的道長(zhǎng)笛辟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)序苏,這世上最難降的妖魔是什么手幢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮忱详,結(jié)果婚禮上围来,老公的妹妹穿的比我還像新娘。我一直安慰自己匈睁,他們只是感情好监透,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著航唆,像睡著了一般胀蛮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糯钙,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天粪狼,我揣著相機(jī)與錄音,去河邊找鬼任岸。 笑死再榄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的享潜。 我是一名探鬼主播困鸥,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剑按!你這毒婦竟也來(lái)了疾就?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吕座,失蹤者是張志新(化名)和其女友劉穎虐译,沒(méi)想到半個(gè)月后瘪板,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體吴趴,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年侮攀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锣枝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兰英,死狀恐怖撇叁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畦贸,我是刑警寧澤陨闹,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布楞捂,位于F島的核電站,受9級(jí)特大地震影響趋厉,放射性物質(zhì)發(fā)生泄漏寨闹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一君账、第九天 我趴在偏房一處隱蔽的房頂上張望繁堡。 院中可真熱鬧,春花似錦乡数、人聲如沸椭蹄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绳矩。三九已至,卻和暖如春劫侧,著一層夾襖步出監(jiān)牢的瞬間埋酬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工烧栋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留写妥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓审姓,卻偏偏與公主長(zhǎng)得像珍特,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子魔吐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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