前言:
我負責努力,其余交給運氣。
正文:
閑暇之余,把線程的問題整理一下,感覺可能會有點長,所以先自分一下章節(jié),我將會按照以下幾個小節(jié)來展開描述:
1.多線程的基本概念
2.線程的狀態(tài)與生命周期
3.多線程的對比:pthread、NSThread、GCD、NSOperation
4.NSThread的使用
5.GCD的理解與使用
6.NSOperation的理解與使用
一、多線程的基本概念
進程:是計算機中的程序關(guān)于某數(shù)據(jù)集合上的一次運行活動,是系統(tǒng)進行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在當代面向線程設(shè)計的計算機結(jié)構(gòu)中,進程是線程的容器。簡單來說,進程可以看成App執(zhí)行的一個實例,一個進程可以擁有多個線程。線程與進程的一個主要區(qū)別是,同一進程內(nèi)的多個線程會共享部分狀態(tài),多個線程可以讀寫同一塊內(nèi)存(一個進程無法直接訪問另一進程的內(nèi)存)。
線程:是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中最少有一個線程,也可以有多個線程,每條線程可執(zhí)行不同的任務(wù)。
主線程:當一個程序啟動時,就有一個進程被操作系統(tǒng)創(chuàng)建,與此同時一個線程也立刻運行,該線程通常叫做程序的主線程,因為它是程序開始時就執(zhí)行的,如果你需要再創(chuàng)建線程,那么創(chuàng)建的線程就是這個主線程的子線程。主線程的重要性體現(xiàn)在兩方面:1.是產(chǎn)生其他子線程的線程;2.通常它必須最后完成執(zhí)行比如執(zhí)行各種關(guān)閉動作。
多線程:在同一時刻,一個CPU只能處理1條線程,但CPU可以在多條線程之間快速的切換,只要切換的足夠快,就造成了多線程一同執(zhí)行的假象。而我們運用多線程的目的在于,避免阻塞主線程;多線程是通過提高資源使用率來提高系統(tǒng)總體的效率。
最后,做個形象的比喻:一個運行的App我們可以看做是奔跑的火車(一個進程),而一個火車最少要有一個火車頭(主線程,負責程序的運行,且一個進程至少包含一個線程),可以有多節(jié)車廂(多線程,每一節(jié)車廂可以看做是子線程,可執(zhí)行不同的任務(wù))。
二、線程的狀態(tài)與生命周期
如下圖所示,線程的生命周期是:新建、就緒、運行、阻塞、死亡:
新建:實例化線程對象
就緒:向線程對象發(fā)送start消息,線程對象被加入可調(diào)度線程池等待CPU調(diào)度。
運行:CPU 負責調(diào)度可調(diào)度線程池中線程的執(zhí)行。線程執(zhí)行完成之前,狀態(tài)可能會在就緒和運行之間來回切換。就緒和運行之間的狀態(tài)變化由CPU負責,程序員不能干預(yù)。
阻塞:當滿足某個預(yù)定條件時,可以使用休眠或鎖,阻塞線程執(zhí)行。sleepForTimeInterval(休眠指定時長),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖等各種鎖)。
死亡:正常死亡,線程執(zhí)行完畢。非正常死亡,當滿足某個條件后,在線程內(nèi)部中止執(zhí)行/在主線程中止線程對象(例如:exit和cancel)
[NSThread exit]:一旦強行終止線程,后續(xù)的所有代碼都不會被執(zhí)行。
[thread cancel]取消:并不會直接取消線程,只是給線程對象添加 isCancelled 標記。
三、多線程的對比:pthread、NSThread、GCD、NSOperation
相比于pthread和NSThread,GCD和NSOperation更常用一些,所以著重說一下GCD和NSOperation:
GCD 和 NSOperation的區(qū)別主要表現(xiàn)在以下幾方面:
GCD是一套 C 語言API,執(zhí)行和操作簡單高效,因此NSOperation底層也通過GCD實現(xiàn),這是他們之間最本質(zhì)的區(qū)別.因此如果希望自定義任務(wù),建議使用NSOperation;
依賴關(guān)系,NSOperation可以設(shè)置操作之間的依賴(可以跨隊列設(shè)置),GCD無法設(shè)置依賴關(guān)系,不過可以通過同步來實現(xiàn)這種效果;
KVO(鍵值對觀察),NSOperation容易判斷操作當前的狀態(tài)(是否執(zhí)行,是否取消等),對此GCD無法通過KVO進行判斷;
優(yōu)先級,NSOperation可以設(shè)置自身的優(yōu)先級,但是優(yōu)先級高的不一定先執(zhí)行,GCD只能設(shè)置隊列的優(yōu)先級,如果要區(qū)分block任務(wù)的優(yōu)先級,需要很復(fù)雜的代碼才能實現(xiàn);
繼承,NSOperation是一個抽象類.實際開發(fā)中常用的是它的兩個子類:NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執(zhí)行任務(wù)可以自由組裝,沒有繼承那么高的代碼復(fù)用度;
效率,直接使用GCD效率確實會更高效,NSOperation會多一點開銷,但是通過NSOperation可以獲得依賴,優(yōu)先級,繼承,鍵值對觀察這些優(yōu)勢,相對于多的那么一點開銷確實很劃算,魚和熊掌不可得兼,取舍在于開發(fā)者自己;
可以隨時取消準備執(zhí)行的任務(wù)(已經(jīng)在執(zhí)行的不能取消),GCD沒法停止已經(jīng)加入queue 的 block(雖然也能實現(xiàn),但是需要很復(fù)雜的代碼)
基于GCD簡單高效,更強的執(zhí)行能力,操作不太復(fù)雜的時候,優(yōu)先選用GCD;而比較復(fù)雜的任務(wù)可以自己通過NSOperation實現(xiàn).
四、NSThread的使用
4.1 NSThread的創(chuàng)建
NSThread有三種創(chuàng)建方式:
- init 創(chuàng)建好之后需要start啟動
- detachNewThreadSelector 創(chuàng)建好之后自動啟動
- performSelectorInBackground 創(chuàng)建好之后直接啟動
直接上代碼:
- (void)showNSThread{
/** 方法一,需要start */
NSThread* thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"1"];
[thread1 start];
/** 方法二,創(chuàng)建好之后自動啟動 */
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"2"];
/** 方法三,隱式創(chuàng)建,直接啟動 */
[self performSelectorInBackground:@selector(doSomething3:) withObject:@"3"];
}
- (void)doSomething1:(NSObject*)obj{
NSLog(@"doSometing1 ---- obj = %@",obj);
}
- (void)doSomething2:(NSObject*)obj{
NSLog(@"doSometing2 ---- obj = %@",obj);
}
- (void)doSomething3:(NSObject*)obj{
NSLog(@"doSometing3 ---- obj = %@",obj);
}
4.2 NSThread的一些屬性、實例方法、類方法**
- 屬性:
/**
每個線程都維護了一個鍵-值的字典,它可以在線程里面的任何地方被訪問。
可以使用該字典來保存一些信息,這些信息在整個線程的執(zhí)行過程中都保持不變。
比如,你可以使用它來存儲在你的整個線程過程中 Run loop 里面多次迭代的狀態(tài)信息。
NSThread實例可以使用一下方法:
NSMutableDictionary *dict = [thread threadDictionary];
*/
@property (readonly, retain) NSMutableDictionary *threadDictionary;
@property double threadPriority ; //優(yōu)先級
/**NSQualityOfService說明:
NSQualityOfServiceUserInteractive:最高優(yōu)先級,主要用于提供交互UI的操作,比如處理點擊事件,繪制圖像到屏幕上
NSQualityOfServiceUserInitiated:次高優(yōu)先級,主要用于執(zhí)行需要立即返回的任務(wù)
NSQualityOfServiceDefault:默認優(yōu)先級,當沒有設(shè)置優(yōu)先級的時候,線程默認優(yōu)先級
NSQualityOfServiceUtility:普通優(yōu)先級,主要用于不需要立即返回的任務(wù)
NSQualityOfServiceBackground:后臺優(yōu)先級,用于完全不緊急的任務(wù)
*/
@property NSQualityOfService qualityOfService; //線程優(yōu)先級
@property (nullable, copy) NSString *name;//線程名稱
@property NSUInteger stackSize ;//線程使用棧區(qū)大小,默認是512K
@property (readonly, getter=isExecuting) BOOL executing;//線程是否正在執(zhí)行
@property (readonly, getter=isFinished) BOOL finished;//線程是否執(zhí)行結(jié)束
@property (readonly, getter=isCancelled) BOOL cancelled;//線程是否可被取消
- 實例方法:
-(void)start; //啟動線程(init創(chuàng)建的線程,需start啟動,不自行運行)
-(BOOL)isMainThread; //是否為主線程
-(void)setName:(NSString *)n; //設(shè)置線程名稱
-(void)cancel ; //取消線程
-(void)main ; //線程的入口函數(shù)
-(void)isExecuting; //判斷線程是否正在執(zhí)行
-(void)isFinished; //判斷線程是否已經(jīng)結(jié)束
-(void)isCancelled; //判斷線程是否撤銷
- 類方法:
+(void)currentThread; //獲取當前線程
+(BOOL)isMultiThreaded; //當前代碼運行所在線程是否是子線程
+(void)sleepUntilDate:(NSDate *)date; //當前代碼所在線程睡到指定時間
+(void)sleepForTimeInterval:(NSTimeInterval)ti; //當前線程睡多長時間
+(void)exit; //退出當前線程
+(double)threadPriority; //設(shè)置當前線程優(yōu)先級
/**給當前線程設(shè)定優(yōu)先級,調(diào)度優(yōu)先級的取值范圍是0.0 ~ 1.0,
默認0.5,值越大,優(yōu)先級越高。*/
+(BOOL)setThreadPriority:(double)p;
/**
線程的調(diào)用都會有函數(shù)的調(diào)用,函數(shù)的調(diào)用就會有棧返回地址的記錄,
在這里返回的是函 數(shù)調(diào)用返回的虛擬地址,
說白了就是在該線程中函數(shù)調(diào)用的虛擬地址的數(shù)組
*/
+(NSArray *)callStackReturnAddresses;
+(NSArray *)callStackSymbols; //同上面的方法一樣,只不過返回的是該線程調(diào)用函數(shù)的名字數(shù)字
注:callStackReturnAddress和callStackSymbols這兩個函數(shù)可以同NSLog聯(lián)合使用來跟蹤線程的函數(shù)調(diào)用情況,是編程調(diào)試的重要手段。
五、GCD的理解與使用
5.1 GCD的基本概念
任務(wù)(block):任務(wù)就是將要在線程中執(zhí)行的代碼,將這段代碼用block封裝好,然后將這個任務(wù)添加到指定的執(zhí)行方式(同步執(zhí)行和異步執(zhí)行),等待CPU從隊列中取出任務(wù)放到對應(yīng)的線程中執(zhí)行。
同步(sync):一個接著一個,前一個沒有執(zhí)行完,后面不能執(zhí)行,不開線程。
異步(async):開啟多個新線程,任務(wù)同一時間可以一起執(zhí)行。異步是多線程的代名詞。
隊列:裝載線程任務(wù)的隊形結(jié)構(gòu)。(系統(tǒng)以先進先出的方式調(diào)度隊列中的任務(wù)執(zhí)行)。在GCD中有兩種隊列:串行隊列和并發(fā)隊列。
并發(fā)隊列:線程可以同時一起進行執(zhí)行。實際上是CPU在多條線程之間快速的切換。(并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效)
串行隊列:線程只能依次有序的執(zhí)行。
GCD總結(jié):添加任務(wù)(線程中執(zhí)行的操作block)且指定執(zhí)行任務(wù)的方式(異步dispatch_async,同步dispatch_sync),并將線程添加進隊列(自己創(chuàng)建或使用全局并發(fā)隊列)。
5.2 隊列的創(chuàng)建方式
使用dispatch_queue_create來創(chuàng)建隊列對象,傳入兩個參數(shù),第一個參數(shù)表示隊列的唯一標識符,可為空。第二個參數(shù)用來表示串行隊列(DISPATCH_QUEUE_SERIAL或零)或并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT)。
//串行隊列
dispatch_queue_t queue1 = dispatch_queue_create("queue1",DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue2 = dispatch_queue_create("queue2",DISPATCH_QUEUE_CONCURRENT);
GCD的隊列還有另外兩種:
- 主隊列:dispatch_get_main_queue(),主隊列負責在主線程上調(diào)度任務(wù),如果在主線程上已經(jīng)有任務(wù)正在執(zhí)行,主隊列會等到主線程空閑后再調(diào)度任務(wù)。通常是返回主線程更新UI的時候使用。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗時操作放在這里
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程進行UI操作
});
});
- 全局并發(fā)隊列:dispatch_get_global_queue(0, 0),全局并發(fā)隊列是就是一個并發(fā)隊列,是為了讓我們更方便的使用多線程。
//全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺優(yōu)先級
//iOS8開始使用服務(wù)質(zhì)量,現(xiàn)在獲取全局并發(fā)隊列時,可以直接傳0
dispatch_get_global_queue(0, 0);
5.3 同步、異步&任務(wù)
- 同步(sync):使用dispatch_sync來表示。
- 異步(async):使用dispatch_async來表示。
- 任務(wù):就是將要在線程中執(zhí)行的代碼,將這段代碼用block封裝好。
// 同步執(zhí)行任務(wù)
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
// 任務(wù)放在這個block里
NSLog(@"我是同步執(zhí)行的任務(wù)");
});
// 異步執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 任務(wù)放在這個block里
NSLog(@"我是異步執(zhí)行的任務(wù)");
});
5.4 GCD的使用
由于有多種隊列(串行、并發(fā)、主隊列)和兩種執(zhí)行方式(同步、異步),所以他們之間有多種組合方式:串行同步、串行異步、并發(fā)同步、并發(fā)異步、主隊列同步、主隊列異步。
- 5.4.1 串行同步:執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)。不開啟新線程。
/** 串行同步 */
- (void)serialSynchronization{
// 串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 同步執(zhí)行
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行同步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行同步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行同步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
}
執(zhí)行結(jié)果為順序執(zhí)行,且沒有開辟新線程,都在主線程,阻塞了主線程,如圖:
- 5.4.2 串行異步:開啟新線程,但因為任務(wù)是串行的,所以還是按順序執(zhí)行任務(wù)。
/** 串行異步 */
- (void)serialAsynchronous {
// 串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 異步執(zhí)行
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行異步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行異步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"串行異步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
}
執(zhí)行結(jié)果如下,因為是串行隊列,所以異步線程按照順序執(zhí)行;但是異步開辟了新的子線程,所以沒有阻塞主線程:
- 5.4.3 并發(fā)同步
/** 并發(fā)同步 */
- (void)concurrentSynchronization {
// 并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 同步執(zhí)行
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)同步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)同步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)同步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
}
執(zhí)行結(jié)果如下,雖然我們選擇的并發(fā)隊列,但是因為是同步線程,所以任務(wù)依然是順序執(zhí)行,沒有開辟新線程,依然在主線程上執(zhí)行,且阻塞了主線程:
- 5.4.4 并發(fā)異步
/** 并發(fā)異步 */
- (void)concurrentAsynchronization {
// 并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// 異步執(zhí)行
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)異步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)異步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"并發(fā)異步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
}
執(zhí)行結(jié)果如下,執(zhí)行順序無序(并發(fā))、開辟了多條子線程、沒有阻塞主線程:
- 5.4.5 主隊列同步
/** 主隊列同步 */
- (void)syncMain {
// 主隊列
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列同步1,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列同步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)3");
dispatch_sync(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列同步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)4");
}
執(zhí)行結(jié)果如下,xcode8之后,不會阻塞,會崩潰。不過話說回來,主隊列同步造成死鎖,我們都知道是因為相互等待,可是阻塞、等待的是誰呢?這里要說的是:阻塞的是隊列, 不是線程,我們可能會疑惑,為什么其他串行同步不會死鎖:輸出block確實也在主線程執(zhí)行, 如果sync阻塞的是主線程, 那按理這樣確實會死鎖, 但實際上sync阻塞的是主隊列, 而不是主線程。其他串行同步運行結(jié)果是, dispatch_sync運行在主隊列, 而其block運行在queue隊列, 雖然兩個隊列都是由主線程執(zhí)行, 由于dispatch_sync阻塞的只是主隊列, block在queue隊列由主線程執(zhí)行完并返回, 從而dispatch_sync返回, 之后主隊列結(jié)束阻塞繼續(xù)執(zhí)行下面任務(wù). 此處queue隊列是運行在主線程的另一串行隊列(是不是很懵~~ 哈哈~~ 看下面模擬阻塞代碼會清楚一些)。
我們自己模擬出主隊列阻塞可能更容易理解:
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//可以把queue理解為主隊列
dispatch_sync(queue, ^{//可以把這理解為主線程
dispatch_sync(queue, ^{//主線程中主隊列同步
});
});
這樣就把串行隊列queue阻塞了.
- 5.4.6 主隊列異步
/** 主隊列異步 */
- (void)asyncMain {
// 主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列異步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列異步2,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)1");
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"主隊列異步3,%@",[NSThreadcurrentThread]);
}
});
NSLog(@"主線程執(zhí)行函數(shù)2");
}
執(zhí)行結(jié)果如下,在主線程中任務(wù)按順序執(zhí)行,但并沒有阻塞主線程:
我們通過上面六種組合,可以看到,同步不論串行還是并發(fā),都沒有開辟新線程,運行在主線程上,且都是順序執(zhí)行,所以并沒有并發(fā)效果。異步不論串行還是并發(fā),都開啟了新線程(子線程),但是可見的是,多條異步串行,只開辟了一個子線程,多條異步并發(fā),開啟了多條子線程。主隊列異步,并沒有開啟子線程,且為有序串行執(zhí)行,但并沒有阻塞主線程。
-
5.4.7 GCD線程間的通訊
開發(fā)中需要在主線程上進行UI的相關(guān)操作,通常會把一些耗時的操作放在其他線程,比如說圖片文件下載等耗時操作。當完成了耗時操作之后,需要回到主線程進行UI的處理,這里就用到了線程之間的通訊。
- (IBAction)communicationBetweenThread:(id)sender {
// 異步
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗時操作放在這里,例如下載圖片。(運用線程休眠兩秒來模擬耗時操作)
[NSThread sleepForTimeInterval:2];
NSString *picURLStr = @"https://123.jpg";
NSURL *picURL = [NSURL URLWithString:picURLStr];
NSData *picData = [NSData dataWithContentsOfURL:picURL];
UIImage *image = [UIImage imageWithData:picData];
// 回到主線程處理UI
dispatch_async(dispatch_get_main_queue(), ^{
// 在主線程上添加圖片
self.imageView.image = image;
});
});
}
-
5.4.8 GCD柵欄
當任務(wù)需要異步進行,但是這些任務(wù)需要分成兩組來執(zhí)行,第一組完成之后才能進行第二組的操作。這時候就用了到GCD的柵欄方法dispatch_barrier_async和dispatch_barrier_sync
詳情可查閱官方文檔。他們的不同之處在于:
dispatch_barrier_sync 將自己的任務(wù)插入到隊列的時候,需要等待自己的任務(wù)結(jié)束之后才會繼續(xù)插入被寫在它后面的任務(wù),然后執(zhí)行它們。
dispatch_barrier_async 將自己的任務(wù)插入到隊列之后,不會等待自己的任務(wù)結(jié)束,它會繼續(xù)把后面的任務(wù)插入到隊列,然后等待自己的任務(wù)結(jié)束后才執(zhí)行后面任務(wù)。
實際上類似于同步異步,下面以dispatch_barrier_async
為例:
- (void)barrierGCD {
// 并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 異步執(zhí)行
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"柵欄:并發(fā)異步1,%@",[NSThreadcurrentThread]);
}
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"柵欄:并發(fā)異步2,%@",[NSThreadcurrentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"------------barrier------------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"柵欄:并發(fā)異步3,%@",[NSThreadcurrentThread]);
}
});
dispatch_async(queue, ^{
for(inti =0; i <3; i++) {
NSLog(@"柵欄:并發(fā)異步4,%@",[NSThreadcurrentThread]);
}
});
}
執(zhí)行結(jié)果如下,開啟了多條線程,所有任務(wù)都是并發(fā)異步進行。但是第一組完成之后,才會進行第二組的操作
需要注意的是:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block)
,柵欄函數(shù)中傳入的參數(shù)隊列必須是由 dispatch_queue_create
方法創(chuàng)建的隊列,否則,與dispatch_async
無異,起不到“柵欄”的作用了,對于dispatch_barrier_sync
也是同理。
-
5.4.9 GCD隊列組
平時在進行多線程處理任務(wù)時,有時候希望多個任務(wù)之間存在著一種聯(lián)系,希望在所有的任務(wù)執(zhí)行完后做一些總結(jié)性處理。那么就可以將多個任務(wù)放在一個任務(wù)組中進行統(tǒng)一管理。dispatch提供了相應(yīng)的API供我們完成這一需求。
(1)dispatch_group_t相關(guān)屬性介紹:
1.dispatch_group_async(group, queue, block);
將block任務(wù)添加到queue隊列,并被group組管理
2.dispatch_group_enter(group);
聲明dispatch_group_enter(group)下面的任務(wù)由group組管理,group組的任務(wù)數(shù)+1
3.dispatch_group_leave(group);
相應(yīng)的任務(wù)執(zhí)行完成,group組的任務(wù)數(shù)-1
4.dispatch_group_create();
創(chuàng)建一個group組
5.dispatch_group_wait(group1, DISPATCH_TIME_FOREVER);
當前線程暫停,等待dispatch_group_wait(group1, DISPATCH_TIME_FOREVER)上面的任務(wù)執(zhí)行完成后,線程才繼續(xù)執(zhí)行。
6.dispatch_group_notify(group1, queue1,block);
監(jiān)聽group組中任務(wù)的完成狀態(tài),當所有的任務(wù)都執(zhí)行完成后,觸發(fā)block塊,執(zhí)行總結(jié)性處理。
(2)常見用法的區(qū)別
在使用group組處理任務(wù)時,常見的有兩種組合。
其一:
dispatch_group_async(group, queue, block);
dispatch_group_notify(group1, queue1, block);
在這種組合下,根據(jù)任務(wù)是同步、異步又分為兩種,這兩種組合的執(zhí)行代碼與運行結(jié)果如下:
同步任務(wù)時:
- (void)groupSync{
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group1 = dispatch_group_create();
dispatch_group_async(group1, queue1, ^{
dispatch_sync(queue1, ^{
for(NSInteger i =0; i<3; i++) {
NSLog(@"同步任務(wù)1-:%ld",(long)i);
}
});
});
dispatch_group_async(group1, queue1, ^{
dispatch_sync(queue1, ^{
for(NSInteger i =0; i<3; i++) {
sleep(1);
NSLog(@"同步任務(wù)2-:%ld",(long)i);
}
});
});
//等待上面的任務(wù)全部完成后,會收到通知執(zhí)行block中的代碼 (不會阻塞線程)
dispatch_group_notify(group1, queue1, ^{
NSLog(@"全部任務(wù)執(zhí)行完成");
});
}
異步任務(wù)時:
- (void)groupAsync{
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group1 = dispatch_group_create();
dispatch_group_async(group1, queue1, ^{
dispatch_async(queue1, ^{
for(NSInteger i =0; i<3; i++) {
sleep(1);
NSLog(@"異步任務(wù)1,%ld",(long)i);
}
});
});
dispatch_group_async(group1, queue1, ^{
dispatch_async(queue1, ^{
for(NSInteger i =0; i<3; i++) {
sleep(1);
NSLog(@"異步任務(wù)2,%ld",(long)i);
}
});
});
//等待上面的任務(wù)全部完成后,會收到通知執(zhí)行block中的代碼 (不會阻塞線程)
dispatch_group_notify(group1, queue1, ^{
NSLog(@"全部任務(wù)執(zhí)行完成");
});
}
結(jié)論:dispatch_group_async(group, queue, block) 和 dispatch_group_notify(group1, queue1, block) 組合在執(zhí)行同步任務(wù)時正常,在執(zhí)行異步任務(wù)時不正常。
其二:
dispatch_group_enter(group);
dispatch_group_leave(group);
dispatch_group_notify(group1, queue1,block);
同樣的在這種組合下,根據(jù)任務(wù)是同步、異步也分為兩種,這兩種組合的執(zhí)行代碼與運行結(jié)果如下:
同步任務(wù)時:
- (void)groupSyncEnter {
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group2 = dispatch_group_create();
dispatch_group_enter(group2);
dispatch_sync(queue2, ^{
for(NSInteger i =0; i<3; i++) {
NSLog(@"同步任務(wù)1,%ld",(long)i);
}
dispatch_group_leave(group2);
});
dispatch_group_enter(group2);
dispatch_sync(queue2, ^{
for(NSInteger i =0; i<3; i++) {
NSLog(@"同步任務(wù)2,%ld",(long)i);
}
dispatch_group_leave(group2);
});
//等待上面的任務(wù)全部完成后,會收到通知執(zhí)行block中的代碼 (不會阻塞線程)
dispatch_group_notify(group2, queue2, ^{
NSLog(@"全部任務(wù)執(zhí)行完成");
});
}
異步任務(wù)時:
- (void)groupAsyncEnter{
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group2 = dispatch_group_create();
dispatch_group_enter(group2);
dispatch_async(queue2, ^{
for(NSInteger i =0; i<3; i++) {
NSLog(@"異步任務(wù)1,%ld",(long)i);
}
dispatch_group_leave(group2);
});
dispatch_group_enter(group2);
dispatch_async(queue2, ^{
for(NSInteger i =0; i<3; i++) {
NSLog(@"異步任務(wù)2,%ld",(long)i);
}
dispatch_group_leave(group2);
});
//等待上面的任務(wù)全部完成后,會收到通知執(zhí)行block中的代碼 (不會阻塞線程)
dispatch_group_notify(group2, queue2, ^{
NSLog(@"全部任務(wù)執(zhí)行完成");
});
}
結(jié)論:dispatch_group_enter(group)、dispatch_group_leave(group) 和dispatch_group_notify(group, queue,block) 組合在執(zhí)行同步任務(wù)時正常,在執(zhí)行異步任務(wù)時正常。
-
5.5 GCD一些常用API
除了以上說道的api之外,還有一些常用CGD的api:
/**兩個作用:
第一,變更隊列的執(zhí)行優(yōu)先級;
第二,目標隊列可以成為原隊列的執(zhí)行階層。*/
dispatch_set_target_queue
/**想在指定時間后執(zhí)行處理的情況,可使用dispatch_after函數(shù)來實現(xiàn)。
需要注意的是,dispatch_after函數(shù)并不是在指定時間后執(zhí)行處理,
而只是在指定時間追加處理到Dispatch Queue。*/
dispatch_after
dispatch_apply//第一個參數(shù)為重復(fù)次數(shù),第二個參數(shù)為追加對象的Dispatch,第三個參數(shù)為追加的處理
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
dispatch_suspend(queue)//函數(shù)掛起指定的Dispatch Queue
dispatch_resume(queue)//函數(shù)恢復(fù)指定的Dispatch Queue
dispatch_once//函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次指定處理的API
-
5.6 GCD終止線程
如果還未執(zhí)行的子線程可以用dispatch_block_cancel來取消(需要注意必須用dispatch_block_create創(chuàng)建dispatch_block_t)
- (void)stopSync{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"block1 begin");
[NSThread sleepForTimeInterval:3];
NSLog(@"block1 end");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
//取消執(zhí)行block2
dispatch_block_cancel(block2);
}
運行結(jié)果如下:
但是,如果執(zhí)行中的線程,我們該怎么讓其退出呢?方法如下:
- (void)stopAsync {
__block BOOL isFinish =NO;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for(long i=0; i<10000; i++) {
NSLog(@"正在執(zhí)行第 %ld 次",i);
sleep(1);
if(isFinish ==YES) {
NSLog(@"已經(jīng)終止了");
return;
}
};
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(10 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
NSLog(@"我要停止啦");
isFinish =YES;
});
}
執(zhí)行結(jié)果:
方法很簡單,線程外設(shè)置__block變量,配合線程中return結(jié)束。
六、NSOperation的理解與使用
6.1 NSOperation, NSOperationQueue 簡介
NSOperation需要配合NSOperationQueue來實現(xiàn)多線程;NSOperation,NSOperationQueue 是蘋果提供給我們的一套多線程解決方案,是基于 GCD 更高一層的封裝,完全面向?qū)ο蟆5潜?GCD 更簡單易用,代碼可讀性更高。
既然是基于 GCD 的更高一層的封裝。那么,GCD 的一些概念同樣適用于 NSOperation,NSOperationQueue。在 NSOperation,NSOperationQueue 中也有類似于任務(wù)(操作)和隊列(操作隊列)的概念。
-
操作(Operation)
- 執(zhí)行操作的意思,即在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的。在 NSOperation 中,我們使用 NSOperation 子類NSInvocationOperation,NSBlockOperation,或者時自定義子類來封裝操作。
-
操作隊列(Operation Queue)
- 這里的隊列指操作隊列,即用來存放操作的隊列。不同于 GCD中的調(diào)度隊列 FIFO(先進先出)的原則。NSOperationQueue 對于添加到隊列中的操作,首先進入準備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序),由操作之間相對的優(yōu)先級決定(優(yōu)先級是操作對象自身的屬性);
- 操作隊列通過設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來控制并發(fā),串行;
- NSOperationQueue 為我們提供了兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在后臺執(zhí)行。
NSOperation實現(xiàn)多線程的步驟如下:
- 創(chuàng)建操作:先將要執(zhí)行的操作封裝到一個 NSOperation 對象中
- 創(chuàng)建隊列:創(chuàng)建 NSOperationQueue 對象。
- 將操作加入到隊列中:將 NSOpeartion 對象添加到 NSOperationQueue 對象中。
需要注意的是,NSOperation是個抽象類,實際運用時中需要使用它的子類,有三種方式:
- 使用子類NSInvocationOperation
- 使用子類NSBlockOperation
- 定義繼承自NSOperation的子類,通過實現(xiàn)內(nèi)部相應(yīng)的方法來封裝任務(wù)。
6.2 NSOperation的三種創(chuàng)建方式
6.2.1 使用NSInvocationOperation創(chuàng)建
//NSInvocationOperation使用
- (void)useNSInvocationOperation{
//創(chuàng)建NSInvocationOperation并關(guān)聯(lián)方法
NSInvocationOperation* opratin = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction) object:nil];
//執(zhí)行操作
[opratin start];
}
- (void)invocationOperationAction{
NSLog(@"NSInvocationOperation任務(wù),沒有加入隊列==%@", [NSThread currentThread]);
}
執(zhí)行結(jié)果如下,得到結(jié)論:程序在主線程執(zhí)行,沒有開啟新線程。這是因為 NSOperation 多線程的使用需要配合隊列 NSOperationQueue ,在不使用 NSOperationQueue ,單獨使用 NSOperation 的情況下,系統(tǒng)同步執(zhí)行操作。
6.2.2 使用NSBlockOperation創(chuàng)建
//NSBlockOperation使用
- (void)useNSBlockOperation{
//創(chuàng)建NSBlockOperation并把任務(wù)放到block中
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSInvocationOperation任務(wù),沒有加入隊列==%@", [NSThread currentThread]);
}];
//執(zhí)行操作
[operation start];
}
執(zhí)行結(jié)果如下,結(jié)論上同,不在闡述。
但是NSBlockOperation有一個方法addExecutionBlock:,通過這個方法可以讓NSBlockOperation實現(xiàn)多線程。代碼如下:
//NSBlockOperation使用
- (void)useNSBlockOperation{
//創(chuàng)建NSBlockOperation并把任務(wù)放到block中
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
for(int i = 0; i < 3; i++){
NSLog(@"NSInvocationOperation任務(wù)==%@", [NSThread currentThread]);
}
// NSLog(@"NSInvocationOperation任務(wù),沒有加入隊列==%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
for(int i = 0; i < 3; i++){
NSLog(@"addExecutionBlock方法添加任務(wù)1==%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for(int i = 0; i < 3; i++){
NSLog(@"addExecutionBlock方法添加任務(wù)2==%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for(int i = 0; i < 3; i++){
NSLog(@"addExecutionBlock方法添加任務(wù)3==%@", [NSThread currentThread]);
}
}];
//執(zhí)行操作
[operation start];
}
執(zhí)行結(jié)果如下,(第二個執(zhí)行結(jié)果是添加了十個操作,執(zhí)行了兩次出現(xiàn)的結(jié)果...)
得到結(jié)論:
- 通過addExecutionBlock可以為 NSBlockOperation 添加額外的操作。這些操作(包括blockOperationWithBlock中的操作)可以再不同的線程中同時(并發(fā))執(zhí)行。只有當所有相關(guān)的操作已經(jīng)執(zhí)行完成時,才視為完成。
- 如果添加的操作多的話,blockOperationWithBlock:中的操作也可能會在其他線程(非當前線程)中執(zhí)行,這是由系統(tǒng)決定的,并不是說添加到blockOperationWithBlock:中的操作一定會在當前線程中執(zhí)行。(第二張執(zhí)行結(jié)果,好奇的可以試試,嘿嘿...)
- NSBlockOperation是否會開啟新線程,取決于操作的個數(shù)。如果添加的操作的個數(shù)多,就會開啟新線程。開啟的線程數(shù)是由系統(tǒng)來決定的。
6.2.3 使用繼承自NSOperation的子類創(chuàng)建
首先我們定義一個繼承自NSOperation的類,然后重寫它的main方法,之后就可以使用這個子類來進行相關(guān)的操作了。
/*******************"OurNSOperation.h"*************************/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface OurNSOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
/*******************"OurNSOperation.m"*************************/
#import "OurNSOperation.h"
@implementation OurNSOperation
- (void)main{
for(int i = 0; i < 3; i++){
NSLog(@"NSOperation的子類OurNSOperation==%@",[NSThread currentThread]);
}
}
@end
/*******"回到主控制器導(dǎo)入頭文件并使用OurNSOperation"********/
- (void)useOurNSOperation{
//創(chuàng)建OurNSOperation對象
OurNSOperation* operation = [[OurNSOperation alloc] init];
//開始執(zhí)行
[operation start];
}
運行結(jié)果如下,依然是在主線程執(zhí)行。所以,NSOperation是需要配合隊列NSOperationQueue來實現(xiàn)多線程的。
6.3 隊列NSOperationQueue
NSOperationQueue只有兩種隊列:主隊列、自定義隊列。自定義隊列包含了串行和并發(fā)。
主隊列的創(chuàng)建如下:主隊列上的任務(wù)是在主線程執(zhí)行的。
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
自定義隊列(非主隊列)的創(chuàng)建如下:加入到自定義隊列中的任務(wù),默認就是并發(fā),開啟多線程。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
-
需要注意的是:
- 非主隊列(自定義隊列)可以實現(xiàn)串行或并行。
- 隊列NSOperationQueue有一個參數(shù)叫做最大并發(fā)數(shù):maxConcurrentOperationCount。
- maxConcurrentOperationCount默認為-1,直接并發(fā)執(zhí)行,所以加入到‘非主隊列’中的任務(wù)默認就是并發(fā),開啟多線程。
- 當maxConcurrentOperationCount為1時,則表示不開線程,也就是串行。
- 當maxConcurrentOperationCount大于1時,進行并發(fā)執(zhí)行。
- 系統(tǒng)對最大并發(fā)數(shù)有一個限制,所以即使程序員把maxConcurrentOperationCount設(shè)置的很大,系統(tǒng)也會自動調(diào)整。所以把最大并發(fā)數(shù)設(shè)置的很大是沒有意義的。
6.4 NSOperation + NSOperationQueue
NSOperation 需要配合 NSOperationQueue 來實現(xiàn)多線程,所以把任務(wù)加入隊列,這才是NSOperation的常規(guī)使用方式。
6.4.1 addOperation添加任務(wù)到隊列
先創(chuàng)建好任務(wù),然后運用addOperation:<#(nonnull NSOperation *)#>
方法來吧任務(wù)添加到隊列中,示例代碼如下:
- (void)useAddOperation {
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationActions) object:nil];
NSBlockOperation* blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for(int i = 0; i < 3; i++){
NSLog(@"NSBlockOperation任務(wù) == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}];
[queue addOperation:operation];
[queue addOperation:blockOperation];
}
- (void)invocationOperationActions {
for (int i = 0; i < 3; i++){
NSLog(@"NSInvocationOperation任務(wù) == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}
運行結(jié)果如下,可以看出,任務(wù)都是并發(fā)在子線程執(zhí)行的,開啟了新線程。
6.4.2 addOperationWithBlock添加任務(wù)到隊列
這是一個更方便的把任務(wù)添加到隊列的方法,直接把任務(wù)寫在block中,添加到任務(wù)中。
- (void)useAddOperationWithBlock{
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++){
NSLog(@"任務(wù)1 == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++){
NSLog(@"任務(wù)2 == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++){
NSLog(@"任務(wù)3 == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}];
}
執(zhí)行結(jié)果如下,使用 addOperationWithBlock:將操作加入到操作隊列后能夠開啟新線程,進行并發(fā)執(zhí)行。
6.4.3 maxConcurrentOperationCount控制串行執(zhí)行、并發(fā)執(zhí)行
maxConcurrentOperationCount
,叫做最大并發(fā)操作數(shù),用來控制一個特定隊列中可以有多少個操作同時參與并發(fā)執(zhí)行。
注意:這里maxConcurrentOperationCount控制的不是并發(fā)線程的數(shù)量,而是一個隊列中同時能并發(fā)執(zhí)行的最大操作數(shù)。而且一個操作也并非只能在一個線程中運行。(6.3中做過一些介紹,不再闡述)
- (void)useMaxConcurrentOperationCount{
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
//設(shè)置并發(fā)數(shù)
queue.maxConcurrentOperationCount = 1;//串行隊列
// queue.maxConcurrentOperationCount = 4;//并行隊列
// queue.maxConcurrentOperationCount = 100;//并行隊列
// 使用 addOperationWithBlock添加操作到隊列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)1 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)2 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)3 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)4 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)5 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)6 == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
}
-
最大并發(fā)操作數(shù)為1時
最大并發(fā)操作數(shù)為1時 -
最大并發(fā)操作數(shù)為4時
最大并發(fā)操作數(shù)為4時 -
最大并發(fā)操作數(shù)為100時
最大并發(fā)操作數(shù)為100時 可以看出:當最大并發(fā)操作數(shù)為1時,操作是按照順序串行執(zhí)行的,并且一個操作完成之后,下一個操作才開始執(zhí)行。當最大操作并發(fā)數(shù)大于1時,操作是并發(fā)執(zhí)行的,可以同時執(zhí)行多個操作。而且開啟線程數(shù)量是由系統(tǒng)決定的,不需要我們來管理。
6.5 NSOperation的操作依賴
NSOperation有一個非常好用的方法,就是操作依賴。可以從字面意思理解:某一個操作(operation2)依賴于另一個操作(operation1),只有當operation1執(zhí)行完畢,才能執(zhí)行operation2,這時,就是操作依賴大顯身手的時候了。NSOperation 提供了3個接口供我們管理和查看依賴。
-
- (void)addDependency:(NSOperation *)op;
添加依賴,使當前操作依賴于操作 op 的完成 -
- (void)removeDependency:(NSOperation *)op;
移除依賴,取消當前操作對象操作 op 的依賴 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當前操作開始執(zhí)行之前完成執(zhí)行的所有 NSOperation 對象數(shù)組。
代碼示例:A 執(zhí)行完操作,B 才能執(zhí)行操作。
- (void)useAddDependency{
// 創(chuàng)建隊列
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;//并行隊列
// 創(chuàng)建操作
NSBlockOperation* A = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)A == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
NSBlockOperation* B = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)B == %@ 第%d次執(zhí)行",[NSThread currentThread],i); // 打印當前線程
}
}];
// 添加依賴
[B addDependency:A]; // 讓B依賴于A,則先執(zhí)行A,再執(zhí)行B.
// 添加操作到隊列中
[queue addOperation:A];
[queue addOperation:B];
}
執(zhí)行結(jié)果:
按照6.4.3所述,我們設(shè)置了
queue.maxConcurrentOperationCount = 4 ;
本應(yīng)并行執(zhí)行,但是設(shè)置依賴后,B等A執(zhí)行完畢后才執(zhí)行的。
6.6 NSOperation 優(yōu)先級
NSOperation 提供了queuePriority
(優(yōu)先級)屬性,queuePriority
屬性適用于同一操作隊列中的操作,不適用于不同操作隊列中的操作。默認情況下,所有新創(chuàng)建的操作對象優(yōu)先級都是NSOperationQueuePriorityNormal
。但是我們可以通過setQueuePriority:
方法來改變當前操作在同一隊列中的執(zhí)行優(yōu)先級。
// 優(yōu)先級的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
對于添加到隊列中的操作,首先進入準備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對的優(yōu)先級決定(優(yōu)先級是操作對象自身的屬性)。
-
queuePriority
屬性決定了進入準備就緒狀態(tài)下的操作之間的開始執(zhí)行順序。并且,優(yōu)先級不能取代依賴關(guān)系。 - 如果一個隊列中既包含高優(yōu)先級操作,又包含低優(yōu)先級操作,并且兩個操作都已經(jīng)準備就緒,那么隊列先執(zhí)行高優(yōu)先級操作。
- 如果,一個隊列中既包含了準備就緒狀態(tài)的操作,又包含了未準備就緒的操作,未準備就緒的操作優(yōu)先級比準備就緒的操作優(yōu)先級高。那么,雖然準備就緒的操作優(yōu)先級低,也會優(yōu)先執(zhí)行。優(yōu)先級不能取代依賴關(guān)系。如果要控制操作間的啟動順序,則必須使用依賴關(guān)系。
6.7 NSOperation,NSOperationQueue 線程間的通信
在 iOS 開發(fā)過程中,我們一般在主線程里邊進行 UI 刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。
//線程間通信
- (void)useMainQueue {
// 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加操作
[queue addOperationWithBlock:^{
// 異步執(zhí)行耗時操作
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)1 == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1]; // 模擬耗時操作
NSLog(@"任務(wù)2 == %@ 第%d次執(zhí)行",[NSThread currentThread],i);
}
}];
}];
}
執(zhí)行結(jié)果:
可以看出:通過線程間的通信,先在其他線程中執(zhí)行操作,等操作執(zhí)行完了之后再回到主線程執(zhí)行主線程的相應(yīng)操作。
6.8 NSOperation、NSOperationQueue 常用屬性和方法歸納
6.8.1 NSOperation 常用屬性和方法
-
取消操作方法
-
- (void)cancel;
可取消操作,實質(zhì)是標記 isCancelled 狀態(tài)。
-
-
判斷操作狀態(tài)方法
-
- (BOOL)isFinished;
判斷操作是否已經(jīng)結(jié)束。 -
- (BOOL)isCancelled;
判斷操作是否已經(jīng)標記為取消。 -
- (BOOL)isExecuting;
判斷操作是否正在在運行。 -
- (BOOL)isReady;
判斷操作是否處于準備就緒狀態(tài),這個值和操作的依賴關(guān)系相關(guān)。
-
-
操作同步
-
- (void)waitUntilFinished;
阻塞當前線程,直到該操作結(jié)束。可用于線程執(zhí)行順序的同步。 -
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 會在當前操作執(zhí)行完畢時執(zhí)行 completionBlock。 -
- (void)addDependency:(NSOperation *)op;
添加依賴,使當前操作依賴于操作 op 的完成。 -
- (void)removeDependency:(NSOperation *)op;
移除依賴,取消當前操作對操作 op 的依賴。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在當前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組。
-
6.8.2 NSOperationQueue 常用屬性和方法
-
取消/暫停/恢復(fù)操作
-
- (void)cancelAllOperations;
可以取消隊列的所有操作。 -
- (BOOL)isSuspended;
判斷隊列是否處于暫停狀態(tài)。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)。 -
- (void)setSuspended:(BOOL)b;
可設(shè)置操作的暫停和恢復(fù),YES 代表暫停隊列,NO 代表恢復(fù)隊列。
-
-
操作同步
-
- (void)waitUntilAllOperationsAreFinished;
阻塞當前線程,直到隊列中的操作全部執(zhí)行完畢。
-
-
添加/獲取操作
-
- (void)addOperationWithBlock:(void (^)(void))block;
向隊列中添加一個 NSBlockOperation 類型操作對象。 -
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向隊列中添加操作數(shù)組,wait 標志是否阻塞當前線程直到所有操作結(jié)束 -
- (NSArray *)operations;
當前在隊列中的操作數(shù)組(某個操作執(zhí)行結(jié)束后會自動從這個數(shù)組清除)。 -
- (NSUInteger)operationCount;
當前隊列中的操作數(shù)。
-
-
獲取隊列
+ (id)currentQueue;
獲取當前隊列,如果當前線程不是在 NSOperationQueue 上運行則返回 nil。+ (id)mainQueue;
獲取主隊列。
注意
這里的暫停和取消(包括操作的取消和隊列的取消)并不代表可以將當前的操作立即取消,而是當當前的操作執(zhí)行完畢之后不再執(zhí)行新的操作。
暫停和取消的區(qū)別在于:暫停操作之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行;而取消操作之后,所有的操作都清空了,無法再接著執(zhí)行剩下的操作。
總結(jié):
借鑒很多內(nèi)容,但是有些忘了是誰的,就不一一列舉了。文章有點長,希望大家能耐心的看下來,如果發(fā)現(xiàn)問題,也希望大家積極指正,如有不足,也懇請大家告知,謝謝。
參考資料
參考文章
iOS多線程全套:線程生命周期,多線程的四種解決方案,線程安全問題,GCD的使用,NSOperation的使用
iOS 多線程: [NSOperation NSOperationQueue] 詳解