iOS多線程:GCD詳解

多線程開發(fā)是日常開發(fā)任務(wù)中不可缺少的一部分鸠蚪,在iOS開發(fā)中常用到的多線程開發(fā)技術(shù)有GCD襟衰、NSOperation楔壤、NSThread拷况,本文主要講解多線系列文章中關(guān)于NSOperation的相關(guān)知識和使用詳解。

  1. iOS多線程:GCD詳解
  2. iOS多線程:NSOperation詳解

1梗顺、GCD簡介

GCD對于iOS開發(fā)者來說并不陌生泡孩,在實際開發(fā)中我們會經(jīng)常用到GCD進行多線程的處理,那么GCD是什么呢寺谤?

Grand Central Dispatch(GCD) 是 Apple 開發(fā)的一個多核編程的較新的解決方法仑鸥。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng)。它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù)变屁。在 Mac OS X 10.6 雪豹中首次推出眼俊,也可在 iOS 4 及以上版本使用。

GCD有著很明顯的優(yōu)勢粟关,正是這些優(yōu)勢才使得GCD在處理多線程問題有著舉足輕重的地位疮胖。

  1. GCD是apple為多核的并行運算提出的解決方案。
  2. GCD能較好的利用CPU內(nèi)核資源。
  3. GCD不需要開發(fā)者去管理線程的生命周期澎灸。
  4. 使用簡便谷市,開發(fā)者只需要告訴GCD執(zhí)行什么任務(wù),并不需要編寫任何線程管理代碼击孩。

2、GCD任務(wù)和隊列

相信很多初級開發(fā)者會對GCD任務(wù)和隊列之間的關(guān)系理解含糊不清鹏漆,實際上隊列只是提供了保存任務(wù)的容器巩梢。為了更好的理解GCD,很有必要先了解任務(wù)隊列的概念艺玲。

2.1 GCD任務(wù)

任務(wù)就是需要執(zhí)行的操作括蝠,是GCD中放在block中在線程中執(zhí)行的那段代碼。任務(wù)的執(zhí)行的方式有同步執(zhí)行異步執(zhí)行兩中執(zhí)行方式饭聚。兩者的主要區(qū)別是是否等待隊列的任務(wù)執(zhí)行結(jié)束忌警,以及是否具備開啟新線程的能力

  • 同步執(zhí)行(sync):同步添加任務(wù)到隊列中秒梳,在隊列之前的任務(wù)執(zhí)行結(jié)束之前會一直等待法绵;同步執(zhí)行的任務(wù)只能在當(dāng)前線程中執(zhí)行,不具備開啟新線程的能力酪碘。
  • 異步執(zhí)行(async):異步添加任務(wù)到隊列中朋譬,并需要理會隊列中其他的任務(wù),添加即執(zhí)行兴垦;異步執(zhí)行可以在新的線程中執(zhí)行徙赢,具備開啟新的線程的能力。

2.2 GCD的隊列

隊列:隊列是一種特殊的線性表探越,隊列中允許插入操作的一端稱為隊尾狡赐,允許刪除操作的一端稱為隊頭,是一種先進先出的結(jié)構(gòu)钦幔。在GCD里面隊列是指執(zhí)行任務(wù)的等待隊列枕屉,是用來存放任務(wù)的。按照隊列的結(jié)構(gòu)特性鲤氢,新任務(wù)總是插入在隊列的末尾搀庶,而任務(wù)的執(zhí)行總是從隊列的對頭輸出,每讀取一個任務(wù)铜异,則從隊列中釋放一個任務(wù)哥倔。GCD的隊列分為串行隊列并發(fā)隊列兩種,兩者都符合 FIFO(先進先出)的原則揍庄。兩者的主要區(qū)別是:執(zhí)行順序不同咆蒿,以及開啟線程數(shù)不同。

  • 串行隊列:只開啟一個線程,每次只能有一個任務(wù)執(zhí)行沃测,等待執(zhí)行完畢后才會執(zhí)行下一個任務(wù)缭黔。
  • 并發(fā)隊列:可以讓對個任務(wù)同時執(zhí)行,也就是開啟多個線程蒂破,讓多個任務(wù)同時執(zhí)行馏谨。

兩者之間區(qū)別如下圖所示:


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

3、GCD基本使用

GCD的使用很簡單附迷,首先創(chuàng)建一個隊列惧互,然后向隊列中追加任務(wù),系統(tǒng)會根據(jù)任務(wù)的類型執(zhí)行任務(wù)喇伯。

3.1喊儡、隊列的創(chuàng)建

  1. 隊列的創(chuàng)建很簡單,只需要調(diào)用dispatch_queue_create方法傳入相對應(yīng)的參數(shù)便可稻据。這個方法有兩個參數(shù):
  • 第一個參數(shù)表示隊列的唯一標(biāo)識艾猜,可以傳空。
  • 第二個參數(shù)用來識別是串行隊列還是并發(fā)隊列捻悯。DISPATCH_QUEUE_SERIAL 表示串行隊列匆赃,DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊列。
// 創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("DS.GCDDemo", DISPATCH_QUEUE_SERIAL);
// 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("DS.GCDDemo", DISPATCH_QUEUE_CONCURRENT);
  1. GCD默認(rèn)提供一種全局并發(fā)隊列今缚,調(diào)用 dispatch_get_global_queue方法來獲取全局并發(fā)隊列炸庞。這個方法需要傳入兩個參數(shù)。
  • 第一個參數(shù)是一個長整型類型的參數(shù)荚斯,表示隊列優(yōu)先級埠居,有DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_LOW事期、DISPATCH_QUEUE_PRIORITY_BACKGROUND滥壕、DISPATCH_QUEUE_PRIORITY_DEFAULT四個選項,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT兽泣。
  • 第二個參數(shù)暫時沒用绎橘,用 0 即可。
// 獲取全局并發(fā)隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1. GCD默認(rèn)提供了主隊列唠倦,調(diào)用dispatch_get_main_queue方法獲取称鳞,所有放在主隊列中的任務(wù)都會在主線程中執(zhí)行。主隊列是一種串行隊列稠鼻。
// 主隊
dispatch_queue_t mainQueue = dispatch_get_main_queue();

3.2冈止、創(chuàng)建任務(wù)

GCD調(diào)用dispatch_sync創(chuàng)建同步任務(wù),調(diào)用dispatch_async創(chuàng)建異步任務(wù)候齿。任務(wù)的內(nèi)容都是在block代碼塊中熙暴。

//異步任務(wù)
dispatch_async(queue, ^{
   //異步執(zhí)行的代碼
});
 
//同步任務(wù)   
dispatch_sync(queue, ^{
   //同步執(zhí)行的代碼
});

3.3闺属、任務(wù)和隊列的組合

創(chuàng)建的任務(wù)需要放在隊列中去執(zhí)行,同時考慮到主隊列的特殊性周霉,那么在不考慮嵌套任務(wù)的情況下就會存在同步任務(wù)+串行隊列掂器、同步任務(wù)+并發(fā)隊列、異步任務(wù)+串行隊列俱箱、異步任務(wù)+并發(fā)隊列国瓮、主隊列+同步任務(wù)、主隊列+異步任務(wù)六種組合狞谱,下面我們來分析下這幾種組合乃摹。

  • 同步任務(wù)+串行隊列:同步任務(wù)不會開啟新的線程,任務(wù)串行執(zhí)行芋簿。
  • 同步任務(wù)+并發(fā)隊列:同步任務(wù)不會開啟新的線程,雖然任務(wù)在并發(fā)隊列中璃饱,但是系統(tǒng)只默認(rèn)開啟了一個主線程与斤,沒有開啟子線程,所以任務(wù)串行執(zhí)行荚恶。
  • 異步任務(wù)+串行隊列:異步任務(wù)有開啟新的線程撩穿,任務(wù)串行執(zhí)行。
  • 異步任務(wù)+并發(fā)隊列:異步任務(wù)有開啟新的線程谒撼,任務(wù)并發(fā)執(zhí)行食寡。
  • 主隊列+同步任務(wù):主隊列是一種串行隊列,任務(wù)在主線程中串行執(zhí)行廓潜,將同步任務(wù)添加到主隊列中會造成追加的同步任務(wù)和主線程中的任務(wù)相互等待阻塞主線程抵皱,導(dǎo)致死鎖。
  • 主隊列+異步任務(wù):主隊列是一種串行隊列辩蛋,任務(wù)在主線程中串行執(zhí)行呻畸,即使是追加的異步任務(wù)也不會開啟新的線程,任務(wù)串行執(zhí)行悼院。

下面我們來看看各種組合之間的使用伤为。

3.4、GCD的基礎(chǔ)使用

3.4.1据途、同步任務(wù)+串行隊列
- (void)syncTaskWithSerial {
    NSLog(@"currentThread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
}

打印結(jié)果:
2020-03-12 21:34:25.807965+0800 ThreadDemo[51144:6948582] currentThread:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808231+0800 ThreadDemo[51144:6948582] currentThread-1:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808467+0800 ThreadDemo[51144:6948582] currentThread-2:<NSThread: 0x600001739100>{number = 1, name = main}
2020-03-12 21:34:25.808669+0800 ThreadDemo[51144:6948582] currentThread-3:<NSThread: 0x600001739100>{number = 1, name = main}

從上面代碼運行的結(jié)果可以看出绞愚,并沒有開啟新的線程,任務(wù)是按順序執(zhí)行的颖医。

3.4.2位衩、同步任務(wù)+并發(fā)隊列
- (void)syncTaskWithConcurrent {
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
}

打印結(jié)果:
2020-03-12 21:39:45.931001+0800 ThreadDemo[51225:6953218] current thread:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931259+0800 ThreadDemo[51225:6953218] current thread-1:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931442+0800 ThreadDemo[51225:6953218] current thread-2:<NSThread: 0x600002abe0c0>{number = 1, name = main}
2020-03-12 21:39:45.931606+0800 ThreadDemo[51225:6953218] current thread-3:<NSThread: 0x600002abe0c0>{number = 1, name = main}

從上面代碼運行的結(jié)果可以看出,同步任務(wù)不會開啟新的線程熔萧,雖然任務(wù)在并發(fā)隊列中蚂四,但是系統(tǒng)只默認(rèn)開啟了一個主線程光戈,沒有開啟子線程,所以任務(wù)串行執(zhí)行遂赠。

3.4.3久妆、異步任務(wù)+串行隊列
- (void)asyncTaskWithSeria{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"4");
}

打印結(jié)果:
2020-03-12 21:44:22.369058+0800 ThreadDemo[51283:6957598] current thread:<NSThread: 0x6000031891c0>{number = 1, name = main}
2020-03-12 21:44:22.369279+0800 ThreadDemo[51283:6957598] 4
2020-03-12 21:44:22.369346+0800 ThreadDemo[51283:6958684] current thread-1:<NSThread: 0x6000031acb80>{number = 7, name = (null)}
2020-03-12 21:44:22.369511+0800 ThreadDemo[51283:6958684] current thread-2:<NSThread: 0x6000031acb80>{number = 7, name = (null)}
2020-03-12 21:44:22.369675+0800 ThreadDemo[51283:6958684] current thread-3:<NSThread: 0x6000031acb80>{number = 7, name = (null)}

從上面代碼運行的結(jié)果可以看出,開啟了一個新的線程跷睦,說明異步任務(wù)具備開啟新的線程的能力筷弦,但是由于任務(wù)是在串行隊列中執(zhí)行的,所以任務(wù)是順序執(zhí)行的抑诸。

3.4.4烂琴、異步任務(wù)+并發(fā)隊列
- (void)asyncTaskWithConcurrent{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-4:%@", [NSThread currentThread]);
    });
}

打印結(jié)果:
2020-03-12 21:59:36.511770+0800 ThreadDemo[51635:6976397] current thread:<NSThread: 0x60000024ed00>{number = 1, name = main}
2020-03-12 21:59:36.512015+0800 ThreadDemo[51635:6976575] current thread-2:<NSThread: 0x600000214ec0>{number = 5, name = (null)}
2020-03-12 21:59:36.512011+0800 ThreadDemo[51635:6976577] current thread-1:<NSThread: 0x600000215700>{number = 4, name = (null)}
2020-03-12 21:59:36.512028+0800 ThreadDemo[51635:6976580] current thread-3:<NSThread: 0x60000021f2c0>{number = 6, name = (null)}
2020-03-12 21:59:36.512035+0800 ThreadDemo[51635:6976578] current thread-4:<NSThread: 0x60000023b340>{number = 7, name = (null)}

從上面代碼的運行結(jié)果可以看出,生成了多個線程蜕乡,并且任務(wù)是隨機執(zhí)行(并發(fā)執(zhí)行)的奸绷。

3.4.5、主隊列+同步任務(wù)
-(void)syncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    
    NSLog(@"4");
}

打印結(jié)果:
2020-03-12 22:05:01.689594+0800 ThreadDemo[51754:6982402] currentThread---<NSThread: 0x600003eaed00>{number = 1, name = main}
(lldb)

很明顯上面這段代碼運行崩潰了层玲,這是因為我們在主線程中執(zhí)行 syncTaskWithMain 方法号醉,相當(dāng)于把 syncTaskWithMain 任務(wù)放到了主線程的隊列中。而 同步執(zhí)行會等待當(dāng)前隊列中的任務(wù)執(zhí)行完畢辛块,才會接著執(zhí)行畔派。那么當(dāng)我們把 任務(wù) 1 追加到主隊列中,任務(wù) 1 就在等待主線程處理完 syncTaskWithMain 任務(wù)润绵。而syncMain 任務(wù)需要等待任務(wù) 1 執(zhí)行完畢线椰,這樣就形成了相互等待的情況,產(chǎn)生了死鎖尘盼。

3.4.6憨愉、主隊列+異步任務(wù)
-(void)asyncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    
    NSLog(@"4");
}

打印結(jié)果:
2020-03-12 22:09:49.285203+0800 ThreadDemo[51832:6986908] currentThread---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.285539+0800 ThreadDemo[51832:6986908] 4
2020-03-12 22:09:49.326310+0800 ThreadDemo[51832:6986908] 1---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.326749+0800 ThreadDemo[51832:6986908] 2---<NSThread: 0x600000aff3c0>{number = 1, name = main}
2020-03-12 22:09:49.326988+0800 ThreadDemo[51832:6986908] 3---<NSThread: 0x600000aff3c0>{number = 1, name = main}

從上面代碼的運行結(jié)果可以看出,雖然是異步任務(wù)卿捎,但是并沒有開啟新的線程莱衩,任然是在主線程中執(zhí)行,并且任務(wù)是順序執(zhí)行的娇澎。

3.5笨蚁、任務(wù)嵌套使用

關(guān)于任務(wù)的嵌套使用有多種情況,這里做一個簡單的總結(jié)趟庄,

區(qū)別 『異步執(zhí)行+并發(fā)隊列』嵌套『同一個并發(fā)隊列』 『同步執(zhí)行+并發(fā)隊列』嵌套『同一個并發(fā)隊列』 『異步執(zhí)行+串行隊列』嵌套『同一個串行隊列』 『同步執(zhí)行+串行隊列』嵌套『同一個串行隊列』
同步 沒有開啟新的線程括细,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖卡住不執(zhí)行 死鎖卡住不執(zhí)行
異步 有開啟新線程戚啥,并發(fā)執(zhí)行任務(wù) 有開啟新線程奋单,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1 條),串行執(zhí)行任務(wù) 有開啟新線程(1 條)猫十,串行執(zhí)行任務(wù)

對于『異步執(zhí)行+串行隊列』嵌套『同一個串行隊列』造成死鎖的情況請看如下代碼:

dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
//異步任務(wù)A
dispatch_async(queue, ^{   
    //同步任務(wù)B
    dispatch_sync(queue, ^{  
        NSLog(@"任務(wù)C---%@",[NSThread currentThread]);
    });
});

首先異步任務(wù)A進入到隊列中览濒,同步任務(wù)B對于異步任務(wù)A來說是代碼執(zhí)行部分呆盖,C對于同步任務(wù)B來說是代碼執(zhí)行部分,因為是在串行隊列中贷笛,任務(wù)是串行執(zhí)行的应又,根據(jù)隊列先進先出原則,首先需要把任務(wù)A取出執(zhí)行乏苦,即執(zhí)行B的部分株扛,但是B依賴C的執(zhí)行,而C等待著B執(zhí)行完成后執(zhí)行汇荐,這樣就形成了一個相互等待洞就,造成死鎖卡死。

同步執(zhí)行+串行隊列』嵌套『同一個串行隊列』造成死鎖的情況同理分析掀淘。

4旬蟋、GCD線程間的通信

在 iOS 開發(fā)過程中,我們在主線程進行UI刷新革娄,把圖片下載倾贰、文件上傳、網(wǎng)絡(luò)請求等一些耗時的操作放在其他的線程稠腊,當(dāng)這些耗時的操作完成后需要將數(shù)據(jù)同步給UI躁染,就需要回到主線程刷新UI鸣哀,那么就要用到線程之間的通訊架忌。GCD提供了非常簡便的方法進行線程間的通訊。

- (void)communication {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
        // 模擬耗時操作
        sleep(2);
        // 回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        });
    });
}

打印結(jié)果
2020-03-13 00:06:07.632014+0800 ThreadDemo[53480:7074047] 1---<NSThread: 0x60000063e480>{number = 5, name = (null)}
2020-03-13 00:06:09.633041+0800 ThreadDemo[53480:7073841] 2---<NSThread: 0x60000061a180>{number = 1, name = main}

從上面代碼運行的結(jié)果可以看出我衬,1是在子線程中執(zhí)行的叹放,隔2秒后打印2,2是在主線程中執(zhí)行的挠羔。

5井仰、GCD中的函數(shù)方法

GCD中提供了諸多的函數(shù)方法供開發(fā)者調(diào)用,我們一起來看下這些方法的使用破加。

5.1俱恶、柵欄方法

有的時候我們需要異步執(zhí)行兩組操作,等待第一組執(zhí)行完成后才回去執(zhí)行第二組操作范舀,這個時候柵欄方法就起作用了合是。
柵欄方法(dispatch_barrier_asyncdispatch_barrier_sync)會等前邊追加到隊列中的任務(wù)執(zhí)行完畢后,再將制定的任務(wù)追加到隊列中锭环,然后等到dispatch_barrier_asyncdispatch_barrier_sync方法追加的任務(wù)執(zhí)行完畢后才會去執(zhí)行后邊追加到隊列中的任務(wù)聪全,簡單來說dispatch_barrier_asyncdispatch_barrier_sync將異步任務(wù)分成了兩個組,執(zhí)行完第一組后辅辩,再執(zhí)行自己难礼,然后執(zhí)行隊列中剩余的任務(wù)娃圆。唯一不同的是dispatch_barrier_async不會阻塞線程。

看如下代碼:

- (void)barrierTask {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    NSLog(@"pause");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結(jié)果
2020-03-13 00:31:53.247514+0800 ThreadDemo[54101:7100220] start
2020-03-13 00:31:53.247730+0800 ThreadDemo[54101:7100220] pause
2020-03-13 00:31:53.247809+0800 ThreadDemo[54101:7100396] currentThread-1:<NSThread: 0x600003b8db00>{number = 5, name = (null)}
2020-03-13 00:31:53.247883+0800 ThreadDemo[54101:7100220] end
2020-03-13 00:31:53.247991+0800 ThreadDemo[54101:7100396] currentThread-2:<NSThread: 0x600003b8db00>{number = 5, name = (null)}
2020-03-13 00:31:55.250622+0800 ThreadDemo[54101:7100396] currentThread-3:<NSThread: 0x600003b8db00>{number = 5, name = (null)}

從上面的代碼運行結(jié)果可以看出蛾茉,start讼呢、pause、end都是在2執(zhí)行答應(yīng)的臀稚,說明dispatch_barrier_async并沒有阻塞線程吝岭,3是在2打印兩秒后打印的。

如果把dispatch_barrier_async換成dispatch_barrier_sync打印結(jié)果會是怎么樣呢吧寺?

- (void)barrierTask {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_barrier_sync(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    NSLog(@"pause");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}

打印結(jié)果
2020-03-13 00:35:01.460109+0800 ThreadDemo[54171:7103212] start
2020-03-13 00:35:01.460408+0800 ThreadDemo[54171:7103379] currentThread-1:<NSThread: 0x600002508540>{number = 6, name = (null)}
2020-03-13 00:35:01.460588+0800 ThreadDemo[54171:7103212] currentThread-2:<NSThread: 0x600002570880>{number = 1, name = main}
2020-03-13 00:35:03.461678+0800 ThreadDemo[54171:7103212] pause
2020-03-13 00:35:03.462012+0800 ThreadDemo[54171:7103212] end
2020-03-13 00:35:03.462145+0800 ThreadDemo[54171:7103379] currentThread-3:<NSThread: 0x600002508540>{number = 6, name = (null)}

從上面代碼運行的結(jié)果可以看出窜管,pause和end是在2之后打印的,說明dispatch_barrier_sync阻塞了線程稚机,需要等待dispatch_barrier_sync執(zhí)行完成后才會往后執(zhí)行幕帆。

5.2、延時執(zhí)行方法-dispatch_after

延時執(zhí)行任務(wù)相信對于iOS開發(fā)者來說并不陌生赖条,我們經(jīng)常遇到需要在延后指定之間后執(zhí)行某個操作的需求失乾,那么這種需求用GCD來實現(xiàn)是很方便的。GCD的延時執(zhí)行的函數(shù)是dispatch_after纬乍。需要注意的是:dispatch_after 方法并不是在指定時間之后才開始執(zhí)行處理碱茁,而是在指定時間之后將任務(wù)追加到主隊列中。嚴(yán)格來說仿贬,這個時間并不是絕對準(zhǔn)確的纽竣,但想要大致延遲執(zhí)行任務(wù),dispatch_after 方法是很有效的茧泪。

-(void)afterTask{
    NSLog(@"begin");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@",[NSThread currentThread]);
    });
}

打印結(jié)果
2020-03-13 00:43:47.787769+0800 ThreadDemo[54378:7111012] begin
2020-03-13 00:43:50.788086+0800 ThreadDemo[54378:7111012] after---<NSThread: 0x60000042ddc0>{number = 1, name = main}

從上面代碼的運行結(jié)果可以看出afer是在begin打印后3秒才打印的蜓氨。

5.3、一次性代碼-dispatch_once

GCD提供了只執(zhí)行一次的方法dispatch_once队伟,這個方法在我們創(chuàng)建單例的時候回經(jīng)常用到穴吹。dispatch_once方法可以保證一段代碼在程序運行過程中只被調(diào)用一次,而且在多線程環(huán)境下可以保證線程安全嗜侮。

+ (instancetype)shareInstance{
    static Test *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [Test alloc]init];
    });
    return instance;
}

5.5港令、調(diào)度組

調(diào)度組簡單來說就是把異步執(zhí)行的任務(wù)進行分組,等待所有的分組任務(wù)都執(zhí)行完畢后再回到指定的線程執(zhí)行任務(wù)锈颗。調(diào)用組使用dispatch_group_create來創(chuàng)建一個分組顷霹,dispatch_group_async方法先把任務(wù)添加到隊列中,然后將隊列方到調(diào)度組中宜猜,或者也可以使用dispatch_group_enterdispatch_group_leave捉對實現(xiàn)將隊列添加到調(diào)度組泼返。調(diào)用dispatch_group_notify方法回到指定線程執(zhí)行任務(wù),或者調(diào)用dispatch_group_wait阻塞當(dāng)前線程姨拥。

- (void)groupNotifyTest{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-1:%@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-2:%@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-3:%@", [NSThread currentThread]);
        NSLog(@"group-end");
    });

    //會阻塞線程绅喉,
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"dispatch_group_wait后繼續(xù)執(zhí)行任務(wù)");
}

打印結(jié)果:
2020-03-14 23:58:09.871023+0800 ThreadDemo[77370:8259501] current thread:<NSThread: 0x6000019fe480>{number = 1, name = main}
2020-03-14 23:58:11.874345+0800 ThreadDemo[77370:8260290] thread-2:<NSThread: 0x600001996e80>{number = 7, name = (null)}
2020-03-14 23:58:11.874343+0800 ThreadDemo[77370:8259684] thread-1:<NSThread: 0x6000019a55c0>{number = 5, name = (null)}
2020-03-14 23:58:11.874672+0800 ThreadDemo[77370:8259501] dispatch_group_wait后繼續(xù)執(zhí)行任務(wù)
2020-03-14 23:58:13.877077+0800 ThreadDemo[77370:8259501] thread-3:<NSThread: 0x6000019fe480>{number = 1, name = main}
2020-03-14 23:58:13.877365+0800 ThreadDemo[77370:8259501] group-end

在這里需要說明的一點是dispatch_group_wait渠鸽,該方法需要傳入兩個參數(shù),第一個參數(shù)是group即調(diào)度組柴罐,第二個參數(shù)是timerout即指定等待的時間徽缚。一旦調(diào)用dispatch_group_wait函數(shù),該函數(shù)就處理調(diào)用的狀態(tài)而不返回值,只有當(dāng)函數(shù)的currentThread停止革屠,或到達wait函數(shù)指定的等待的時間凿试,或Dispatch Group中的操作全部執(zhí)行完畢之前,執(zhí)行該函數(shù)的線程停止似芝。當(dāng)指定timeout為DISPATCH_TIME_FOREVER時就意味著永久等待那婉;當(dāng)指定timeout為DISPATCH_TIME_NOW時就意味不用任何等待即可判定屬于Dispatch Group的處理是否全部執(zhí)行結(jié)束。如果dispatch_group_wait函數(shù)返回值不為0党瓮,就意味著雖然經(jīng)過了指定的時間详炬,但Dispatch Group中的操作并未全部執(zhí)行完畢。如果dispatch_group_wait函數(shù)返回值為0寞奸,就意味著Dispatch Group中的操作全部執(zhí)行完畢呛谜。

下面看dispatch_group_enterdispatch_group_leave捉對實現(xiàn)將隊列添加到調(diào)度組的情況。dispatch_group_enter 標(biāo)志著在group的任務(wù)數(shù)+1枪萄,dispatch_group_leave 標(biāo)志著在group中的任務(wù)數(shù)-1隐岛,表示已經(jīng)完成了一個任務(wù)。

- (void)groupEnterTest {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_1:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_2:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_3:%@", [NSThread currentThread]);
        NSLog(@"group_end");
    });
}

打印結(jié)果:
2020-03-15 00:07:17.293333+0800 ThreadDemo[77525:8269621] thread_2:<NSThread: 0x600002434ac0>{number = 7, name = (null)}
2020-03-15 00:07:17.293320+0800 ThreadDemo[77525:8269427] thread_1:<NSThread: 0x60000241b9c0>{number = 3, name = (null)}
2020-03-15 00:07:19.294186+0800 ThreadDemo[77525:8269235] thread_3:<NSThread: 0x600002450b80>{number = 1, name = main}
2020-03-15 00:07:19.294485+0800 ThreadDemo[77525:8269235] group_end

需要注意的是dispatch_group_enterdispatch_group_leave捉對出現(xiàn)的瓷翻。

5.6聚凹、信號量

GCD中的信號量是指的Dispatch Semaphore,是持有計數(shù)的信號逻悠。當(dāng)信號量小于0時就會一直等待即阻塞所在線程元践,否則就可以正常執(zhí)行韭脊。信號量可以保持線程的同步童谒,將異步執(zhí)行任務(wù)轉(zhuǎn)換成同步任務(wù)執(zhí)行, 同時保持線程的安全沪羔。

Dispatch Semaphore提供了三個方法:

  • dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量
  • dispatch_semaphore_signal:發(fā)送一個信號饥伊,讓信號總量加 1
  • dispatch_semaphore_wait:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程)蔫饰,否則就可以正常執(zhí)行琅豆。
- (void)semaphoreTest {
   dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int a = 0;
    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"里面的a的值:%d-----%@", a, [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
            a++;
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }
    NSLog(@"外面的a的值:%d", a);
}

打印結(jié)果:
2020-03-15 00:44:53.005899+0800 ThreadDemo[78303:8318841] 里面的a的值:0-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006161+0800 ThreadDemo[78303:8318841] 里面的a的值:1-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006354+0800 ThreadDemo[78303:8318841] 里面的a的值:2-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006551+0800 ThreadDemo[78303:8318841] 里面的a的值:3-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006727+0800 ThreadDemo[78303:8318841] 里面的a的值:4-----<NSThread: 0x600003c222c0>{number = 3, name = (null)}
2020-03-15 00:44:53.006862+0800 ThreadDemo[78303:8318672] 外面的a的值:5

5.7、調(diào)度源-Dispatch_source

調(diào)度源是協(xié)調(diào)特殊低級別系統(tǒng)事件處理的基本數(shù)據(jù)類型篓吁。GCD支持諸如定時器調(diào)度源茫因、信號調(diào)度源、描述符調(diào)度源杖剪、進程調(diào)度源冻押、端口調(diào)度源驰贷、自定義調(diào)度源等。調(diào)度源有這一系列的成熟API洛巢,在這里就不多做描述括袒,詳細可以查閱官方的文檔Dispatch Sources

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稿茉,一起剝皮案震驚了整個濱河市锹锰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌漓库,老刑警劉巖恃慧,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渺蒿,居然都是意外死亡糕伐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門蘸嘶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來良瞧,“玉大人,你說我怎么就攤上這事训唱∪祢牵” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵况增,是天一觀的道長赞庶。 經(jīng)常有香客問我,道長澳骤,這世上最難降的妖魔是什么歧强? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮为肮,結(jié)果婚禮上摊册,老公的妹妹穿的比我還像新娘。我一直安慰自己颊艳,他們只是感情好茅特,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棋枕,像睡著了一般白修。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上重斑,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天兵睛,我揣著相機與錄音,去河邊找鬼。 笑死祖很,一個胖子當(dāng)著我的面吹牛累盗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播突琳,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼若债,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拆融?” 一聲冷哼從身側(cè)響起蠢琳,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎镜豹,沒想到半個月后傲须,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡趟脂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年泰讽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昔期。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡已卸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出硼一,到底是詐尸還是另有隱情累澡,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布般贼,位于F島的核電站愧哟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏哼蛆。R本人自食惡果不足惜蕊梧,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腮介。 院中可真熱鬧肥矢,春花似錦、人聲如沸萤厅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惕味。三九已至,卻和暖如春玉锌,著一層夾襖步出監(jiān)牢的瞬間名挥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工主守, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禀倔,地道東北人榄融。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像救湖,于是被迫代替她去往敵國和親愧杯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345