前言:
最近想回顧一下多線程問題,看到一篇文章寫的非常詳細,為了便于以后查找以及加深印象,就照著原文摘錄了下文,原著勿怪:
原文地址:http://www.cocoachina.com/ios/20170707/19769.html
一,多線程的基本概念
進程:? 可以理解成一個運行中的應用程序,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎,主要管理資源.
線程:? 是進程的基本執行單元,一個進程對應多個線程.
主線程:? 處理UI,所有更新UI的操作都必須在主線程中執行,不要把耗時操作放在主線程,會卡界面.
多線程:? 在同一時刻,一個CPU只能處理1條線程,但是CPU可以在多條線程之間快速切換,只要切換的足夠快,就造成了多線程一同執行的假象.
多線程是通過提高資源使用率來提高總體的效率.
我們運用多線程的目的是:將耗時操作放在后臺運行.
二,線程狀態與生命周期
下圖是線程狀態示意圖,從圖中可以看出線程的生命周期是:? 新建 -就緒 -運行 - 阻塞 - 死亡
下面是生命周期的每一步:
新建:?? 實例化線程對象.
就緒:? 向線程對象發送start消息,線程對象被加入線程池等待CPU調度.
運行:? CPU負責調度可調度線程池中線程的執行.線程執行完成前,狀態可能會在就緒和運行之間來回切換.就緒和運行之間的狀態變化由CPU負責,程序不能干預.
阻塞:? 當滿足某個預定條件時,可以使用休眠或鎖,阻塞線程執行,sleepForTimeInterval(休眠指定時間), sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖).
還有線程的exit和cancel.
[NSThread exit]:一旦強行終止線程,后續的所有代碼都不會被執行.
[thread cancel]取消:? 并不會直接取消線程,只是給線程添加isCancelled標記.
三,多線程的四種解決方案
多線程的四種解決方案分別是:pthread,NSThread, GCD,NSOperation.
下面是對這四種方案進行了解讀和對比.
四,線程安全問題
當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題.就好比幾個人在同一時修改一個表格,造成數據的錯亂.
解決多線程安全問題的方法
方法一:? 互斥鎖(同步鎖)
@synchronized(鎖對象) {
??????? // 需要鎖定的代碼
? }
判斷的時候鎖對象要存在,如果代碼中只有一個地方需要加鎖,大多時候使用self作為鎖對象,這樣可以避免單獨再創建一個鎖對象.
加了互斥鎖的代碼,當新線程訪問時,如果發現其他線程正在執行鎖定的代碼,新線程就會進入休眠
方法二:? 自旋鎖
加了自旋鎖,當新線程訪問代碼時,如果發現其他線程正在鎖定代碼,新線程會用死循環的方式,一直等待鎖定的代碼執行完成.相當于不停嘗試執行代碼,比較小號性能.
屬性修飾atomic本身就是一把自旋鎖.
下面說一下屬性修飾nonatomic 和atomic
nonatomic 非原子性的,同一時間可以有很多線程讀和寫.
atomic原子屬性(線程安全), 保證同一時間只有一個縣城能夠寫入(但是同一個時間多個線程都可以取值),atomic本身就有一把鎖(自旋鎖).
atomic: 線程安全,需消耗大量的資源
nonatomic: 非線程安全,不過效率更高,一般用nonatomic
五,NSThread的使用
No.1:NTHread創建線程
NSThread有三種創建方式:
init方式
detachNewThreadSelector創建好之后自動自動
pefromSelectorInBackground創建好之后也是直接啟動
/** 方法一, 需要Start */
? ? NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
? ? //線程加入線程池等待CPU調度,時間很快,幾乎是立刻執行
? ? [thread1 start];
/** 方法二, 創建好之后自動啟動 */
? ? [NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
/** 隱式創建, 直接啟動 */
? ? [self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];
No.2:NSThread的類方法
返回當前線程
//?當前線程
[NSThread?currentThread];
NSLog(@"%@",[NSThread?currentThread]);
//?如果number=1,則表示在主線程,否則是子線程
打印結果:{number?=?1,?name?=?main}
阻塞休眠
//休眠多久
[NSThread?sleepForTimeInterval:2];
//休眠到指定時間
[NSThread?sleepUntilDate:[NSDate?date]];
類方法補充
//退出線程
[NSThread?exit];
//判斷當前線程是否為主線程
[NSThread?isMainThread];
//判斷當前線程是否是多線程
[NSThread?isMultiThreaded];
//主線程的對象
NSThread?*mainThread?=?[NSThread?mainThread];
No.3:NSThread的一些屬性
//線程是否在執行
thread.isExecuting;
//線程是否被取消
thread.isCancelled;
//線程是否完成
thread.isFinished;
//是否是主線程
thread.isMainThread;
//線程的優先級,取值范圍0.0到1.0,默認優先級0.5,1.0表示最高優先級,優先級高,CPU調度的頻率高
?thread.threadPriority;
六,GCD的理解與使用
No.1:GCD的特點
GCD會自動利用更多的CPU內核;
GCD自動管理線程的生命周期(創建線程, 調度任務, 銷毀線程);
程序員只需要告訴GCD想要如何執行什么任務,不需要任何線程管理代碼.
No.2: GCD的基本概念
任務 (block) : 任務就是將要在線程中執行的代碼,將這段代碼用block封裝好,然后將這個任務添加到指定的執行方式(同步執行,異步執行), 等待CPU從隊列中取出任務放到對應的線程中執行.
同步 (sync):一個接著一個,前一個沒有執行完, 后面的不能執行,不開線程.
異步 (async) : 開啟多線程, 任務同一時間可以一起執行. 異步是多線程的代名詞.
隊列 :? 裝載線程任務的隊形結. (系統以先進先出的方式調度隊列中的任務執行). 在GCD中有兩種隊列: 串行隊列和并發隊列.
串行隊列 :線程只能依次有序的執行.
并發隊列 :線程可以同時一起執行.實際上是CPU在多條線程之間快速切換.(并發只有在異步(dispatch_async)函數下才有效).
GCD總結 : 將任務(要在線程中執行的操作block) 添加到隊列(自己創建或使用全局并發隊列),并且指定任務的執行方式(異步dispatch_async, 同步dispatch_sync).
No.3 : 隊列的創建方法
使用dispatch_queue_create來創建隊列對象,傳入兩個參數,第一個表示隊列的唯一標示符,可為空.第二個參數用來標示串行隊列(DISPATCH_QUEUE_SERIAL) 或并發隊列 (DISPATCH_QUEUE_CONCURRENT).
//串行隊列
??? dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
??? //并發隊列
??? dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
GCD隊列還有另外兩種:
主隊列,全局并發隊列
主隊列 :主隊列是負責在主線程上調度任務, 如果在主線程上已經有任務正在執行,主隊列會等到主線程空閑后再調度任務, 通常是返回主線程更新UI的時候使用. dispatch_get_main_queue()
dispatch_async(dispatch_get_global_queue(0, 0), ^{
? ? ? ? //耗時操作放在這里
? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{
? ? ? ? ? ? //回到主線程進行UI操作
? ? ? ? });
? ? });
全局并發隊列: 全局并發隊列就是一個并發隊列,是為了讓我們更方便的使用多線程.
dispatch_get_global_queue(0,0).
//全局并發隊列
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
//全局并發隊列的優先級
#define?DISPATCH_QUEUE_PRIORITY_HIGH?2?//?高優先級
#define?DISPATCH_QUEUE_PRIORITY_DEFAULT?0?//?默認(中)優先級
#define?DISPATCH_QUEUE_PRIORITY_LOW?(-2)?//?低優先級
#define?DISPATCH_QUEUE_PRIORITY_BACKGROUND?INT16_MIN?//?后臺優先級
//iOS8開始使用服務質量,現在獲取全局并發隊列時,可以直接傳0
dispatch_get_global_queue(0,?0);
No.4:同步/異步/任務、創建方式
同步 (sync) 使用dispatch_sync來表示
異步 (sync) 使用dispatch_async來表示
任務就是將要在線程中執行的代碼, 將這段代碼用block封裝好.
代碼如下:
//同步執行任務
? ? dispatch_sync(dispatch_get_global_queue(0, 0), ^{
? ? ? ? //任務放在這個block里
? ? ? ? NSLog(@"我是同步執行任務");
? ? });
? ? //異步執行任務
? ? dispatch_async(dispatch_get_global_queue(0, 0), ^{
? ? ? ? //
? ? ? ? //任務放在這個block里
? ? ? ? NSLog(@"我是異步執行任務");
? ? });
No.5: GCD的使用
由于有多種隊列(串行/并發/主隊列) 和兩種執行方式 (同步/異步),所以他們之間可以有多種組合方式.
1, 串行同步
2,串行異步
3,并發同步
4,并發異步
5,主隊列同步
6,主隊列異步
◎串行同步
執行完一個任務,在執行下一個任務,不開啟新線程.
/**?串行同步?*/
-?(void)syncSerial?{
????NSLog(@"\n\n**************串行同步***************\n\n");
????//?串行隊列
????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);
????//?同步執行
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行同步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行同步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行同步3???%@",[NSThread?currentThread]);
????????}
????});
}
輸出結果為順序執行,都在主線程:
串行同步1???{number?=?1,?name?=?main}
串行同步1???{number?=?1,?name?=?main}
串行同步1???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
◎串行異步
開啟新線程,但因為任務是串行的,所以還是按順序執行完任務.
/**?串行異步?*/
-?(void)asyncSerial?{
????NSLog(@"\n\n**************串行異步***************\n\n");
????//?串行隊列
????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);
????// 異步執行
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行異步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行異步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"串行異步3???%@",[NSThread?currentThread]);
????????}
????});
}
輸出結果為順序執行,有不同線程:
串行異步1???{number?=?3,?name?=?(null)}
串行異步1???{number?=?3,?name?=?(null)}
串行異步1???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
◎并發同步
因為是同步的,所以執行完一個任務,再執行下一個任務.不會開啟新線程
/**?并發同步?*/
-?(void)syncConcurrent?{
????NSLog(@"\n\n**************并發同步***************\n\n");
????//?并發隊列
????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
????//?同步執行
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發同步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發同步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發同步3???%@",[NSThread?currentThread]);
????????}
????});
}
輸出結果為順序執行,都在主線程:
并發同步1???{number?=?1,?name?=?main}
并發同步1???{number?=?1,?name?=?main}
并發同步1???{number?=?1,?name?=?main}
并發同步2???{number?=?1,?name?=?main}
并發同步2???{number?=?1,?name?=?main}
并發同步2???{number?=?1,?name?=?main}
并發同步3???{number?=?1,?name?=?main}
并發同步3???{number?=?1,?name?=?main}
并發同步3???{number?=?1,?name?=?main}
◎并發異步
任務交替執行,開啟多線程.
/**?并發異步?*/
-?(void)asyncConcurrent?{
????NSLog(@"\n\n**************并發異步***************\n\n");
????//?并發隊列
????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
????//?同步執行
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發異步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發異步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"并發異步3???%@",[NSThread?currentThread]);
????????}
????});
}
輸出結果為無序執行,有多條線程:
并發異步1???{number?=?3,?name?=?(null)}
并發異步2???{number?=?4,?name?=?(null)}
并發異步3???{number?=?5,?name?=?(null)}
并發異步1???{number?=?3,?name?=?(null)}
并發異步2???{number?=?4,?name?=?(null)}
并發異步3???{number?=?5,?name?=?(null)}
并發異步1???{number?=?3,?name?=?(null)}
并發異步2???{number?=?4,?name?=?(null)}
并發異步3???{number?=?5,?name?=?(null)}
◎ 主隊列同步
如果在主線程中運用這種方式,則會發生死鎖,程序崩潰.
/**?主隊列同步?*/
-?(void)syncMain?{
????NSLog(@"\n\n**************主隊列同步,放到主線程會死鎖***************\n\n");
????//?主隊列
????dispatch_queue_t?queue?=?dispatch_get_main_queue();
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列同步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列同步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列同步3???%@",[NSThread?currentThread]);
????????}
????});
}
主隊列同步造成死鎖的原因
1,如果在主線程中運用主隊列同步,也就是把任務放到主線程的隊列中.
2,而同步對于任務是立刻執行的,那么當把第一個任務放進主隊列時,他就立馬執行
3,可是主線程現在正在處理syncMain方法,任務需要等sybcMain執行完才能執行.
4,sysnMain執行到第一個任務的時候,又要等第一個任務執行完才能往下執行第二個和第三個任務.
5,這樣syncMain方法和第一個任務就開始了互相等待,形成了死鎖.
◎主隊列異步
在主線程中任務按順序執行
/**?主隊列異步?*/
-?(void)asyncMain?{
????NSLog(@"\n\n**************主隊列異步***************\n\n");
????//?主隊列
????dispatch_queue_t?queue?=?dispatch_get_main_queue();
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列異步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列異步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_sync(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"主隊列異步3???%@",[NSThread?currentThread]);
????????}
????});
}
輸出結果為在主線程中按順序執行:
主隊列異步1???{number?=?1,?name?=?main}
主隊列異步1???{number?=?1,?name?=?main}
主隊列異步1???{number?=?1,?name?=?main}
主隊列異步2???{number?=?1,?name?=?main}
主隊列異步2???{number?=?1,?name?=?main}
主隊列異步2???{number?=?1,?name?=?main}
主隊列異步3???{number?=?1,?name?=?main}
主隊列異步3???{number?=?1,?name?=?main}
主隊列異步3???{number?=?1,?name?=?main}
◎GCD之間的通訊
開發中需要在主線程上進行UI的相關操作,通常會把一些耗時的操作放在其他線程,比如說圖片文件下載等耗時操作.當完成了耗時操作之后,需要回到主線程進行UI的處理,這里用到了線程之間的通訊.
-?(IBAction)communicationBetweenThread:(id)sender?{
????//?異步
????dispatch_async(dispatch_get_global_queue(0,?0),?^{
????????//?耗時操作放在這里,例如下載圖片。(運用線程休眠兩秒來模擬耗時操作)
????????[NSThread?sleepForTimeInterval:2];
????????NSString?*picURLStr?=?@"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.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;
????????});
????});
}
上面的代碼實在新開的線程中進行圖片下載,下載完成之后回到主線程顯示圖片.
◎GCD柵欄
當任務需要異步進行,但是這些任務需要分成兩組來完成,第一組完成之后才能進行第二組, 這時候就用到了GCD的柵欄方法
dispatch_barrier_async.
-?(IBAction)barrierGCD:(id)sender?{
????//?并發隊列
????dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
????//?異步執行
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"柵欄:并發異步1???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"柵欄:并發異步2???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_barrier_async(queue,?^{
????????NSLog(@"------------barrier------------%@",?[NSThread?currentThread]);
????????NSLog(@"*******?并發異步執行,但是34一定在12后面?*********");
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"柵欄:并發異步3???%@",[NSThread?currentThread]);
????????}
????});
????dispatch_async(queue,?^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"柵欄:并發異步4???%@",[NSThread?currentThread]);
????????}
????});
}
上面的代碼打印結果如下,開了多條線程,所有任務都是并發異步進行.但是第一組完成之后,才會進行第二組的操作.
柵欄:并發異步1???{number?=?3,?name?=?(null)}
柵欄:并發異步2???{number?=?6,?name?=?(null)}
柵欄:并發異步1???{number?=?3,?name?=?(null)}
柵欄:并發異步2???{number?=?6,?name?=?(null)}
柵欄:并發異步1???{number?=?3,?name?=?(null)}
柵欄:并發異步2???{number?=?6,?name?=?(null)}
?------------barrier------------{number?=?6,?name?=?(null)}
*******?并發異步執行,但是34一定在12后面?*********
柵欄:并發異步4???{number?=?3,?name?=?(null)}
柵欄:并發異步3???{number?=?6,?name?=?(null)}
柵欄:并發異步4???{number?=?3,?name?=?(null)}
柵欄:并發異步3???{number?=?6,?name?=?(null)}
柵欄:并發異步4???{number?=?3,?name?=?(null)}
柵欄:并發異步3???{number?=?6,?name?=?(null)}
◎GCD延時執行
當需要等待一會再執行,就可以用到這個方法了:dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,?(int64_t)(5.0?*?NSEC_PER_SEC)),?dispatch_get_main_queue(),?^{
????//?5秒后異步執行
????NSLog(@"我已經等待了5秒!");
});
GCD實現代碼只執行一次
使用dispatch_once能保證某段代碼在程序運行過程中只被執行1次。可以用來設計單例。
static?dispatch_once_t?onceToken;
dispatch_once(&onceToken,?^{
????NSLog(@"程序運行過程中我只執行了一次!");
});
◎GCD快速迭代
GCD有一個快速迭代的方法dispatch_apply, dispatch_apply可以同時遍歷多個數字
-?(IBAction)applyGCD:(id)sender?{
????NSLog(@"\n\n**************?GCD快速迭代?***************\n\n");
????//?并發隊列
????dispatch_queue_t?queue?=?dispatch_get_global_queue(0,?0);
????//?dispatch_apply幾乎同時遍歷多個數字
????dispatch_apply(7,?queue,?^(size_t?index)?{
????????NSLog(@"dispatch_apply:%zd======%@",index,?[NSThread?currentThread]);
????});
}
打印結果如下:
dispatch_apply:0======{number?=?1,?name?=?main}
dispatch_apply:1======{number?=?1,?name?=?main}
dispatch_apply:2======{number?=?1,?name?=?main}
dispatch_apply:3======{number?=?1,?name?=?main}
dispatch_apply:4======{number?=?1,?name?=?main}
dispatch_apply:5======{number?=?1,?name?=?main}
dispatch_apply:6======{number?=?1,?name?=?main}
◎GCD隊列組
異步執行幾個耗時操作,當這幾個操作都完成之后再執行另一個操作,就可以用到隊列組了.
隊列組有下面幾個特點:
1,所有的任務會并發的執行(不按順序).
2,所有的異步函數都添加到隊列中,然后再納入隊列組的監聽范圍.
3,使用dispatch_group_notify函數,來監聽上面的任務是否完成,如果完成,就調用這個方法.
隊列組示例代碼:
- (IBAction)groupAction:(id)sender {
? ? NSLog(@"\n\n**************GCD隊列組***************\n");
? ? dispatch_group_t group = dispatch_group_create();
? ? dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
? ? dispatch_group_async(group, queue, ^{
? ? ? ? NSLog(@"隊列組:有一個耗時操作完成!");
? ? });
? ? dispatch_group_async(group, queue, ^{
? ? ? ? NSLog(@"隊列組:有一個耗時操作完成!");
? ? });
? ? dispatch_group_async(group, queue, ^{
? ? ? NSLog(@"隊列組:有一個耗時操作完成!");
? ? });
? ? dispatch_group_notify(group, queue, ^{
? ? ? ? NSLog(@"隊列組:前面的耗時操作都完成了,回到主線程進行相關操作");
? ? });
}
打印結果如下:
隊列組:有一個耗時操作完成!
隊列組:有一個耗時操作完成!
隊列組:有一個耗時操作完成!
隊列組:前面的耗時操作都完成了,回到主線程進行相關操作
至此,GCD的相關操作內容敘述完畢,下面繼續學習NSOperation.
七, NSOperation的理解和使用
No.1: ?NSOperation簡介
NSOperation是基于GCD之上的更高一層封裝,NSOperation需要配合NSOperationQueue來實現多線程.
NSOperation實現多線程步驟如下:
1,創建任務: 先將需要執行的操作封裝到NSOperation對象中.
2,創建隊列: 創建NSOperationQueue.
3,將任務加入到隊列中: 將NSOperation對象添加到NSOperationQueue中
需要注意的是,NAOperation是個抽象類,實際運用時需要使用它的子類,有三種方式:
1,使用子類NSInvocationOperation
2,使用子類NSBlockOperation
3,定義繼承自NSOperation的子類,通過內部響應的方法來封裝任務.
No.2:NSOperation的三種創建方式
◎NSInvocationOperation的使用
創建NSInvocationd對象,并關聯方法,之后start.
-?(void)testNSInvocationOperation?{
????//?創建NSInvocationOperation
????NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperation)?object:nil];
????//?開始執行操作
????[invocationOperation?start];
}
-?(void)invocationOperation?{
????NSLog(@"NSInvocationOperation包含的任務,沒有加入隊列========%@",?[NSThread?currentThread]);
}
打印結果如下,得到結論: 程序在主線程執行,沒有開啟新線程.
這是因為NSOperation多線程的使用需要配合隊列NSOperationQueue,后面會講到NSOperationQueue的使用.
NSInvocationOperation包含的任務,沒有加入隊列========{number?=?1,?name?=?main}
◎NSBlockOperation的使用
把任務放到NSBlockOperation的block中,然后start.
-?(void)testNSBlockOperation?{
????//?把任務放到block中
????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
????????NSLog(@"NSBlockOperation包含的任務,沒有加入隊列========%@",?[NSThread?currentThread]);
????}];
????[blockOperation?start];
}
執行結果如下,可以看出:主線程執行,沒有開啟新線程.
同樣的,NSBlockOperation可以配合隊列NSOperationQueue來實現多線程.
NSBlockOperation包含的任務,沒有加入隊列========{number?=?1,?name?=?main}
但是NSBlockOperation有一個addExecutionBlock:,通過這個方法可以讓NSBlockOperation實現多線程.
-?(void)testNSBlockOperationExecution?{
????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
????????NSLog(@"NSBlockOperation運用addExecutionBlock主任務========%@",?[NSThread?currentThread]);
????}];
????[blockOperation?addExecutionBlock:^{
????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務1========%@",?[NSThread?currentThread]);
????}];
????[blockOperation?addExecutionBlock:^{
????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務2========%@",?[NSThread?currentThread]);
????}];
????[blockOperation?addExecutionBlock:^{
????????NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務3========%@",?[NSThread?currentThread]);
????}];
????[blockOperation?start];
}
執行結果如下,可以看出,NSBlockOperation創建時block中的任務是在主線程中執行,而運行addExecutionBlock加入的任務是在子線程執行的.
NSBlockOperation運用addExecutionBlock========{number?=?1,?name?=?main}
addExecutionBlock方法添加任務1========{number?=?3,?name?=?(null)}
addExecutionBlock方法添加任務3========{number?=?5,?name?=?(null)}
addExecutionBlock方法添加任務2========{number?=?4,?name?=?(null)}
◎運用繼承自NSOperation的子類
首先我們定義一個繼承自NSOperation的類,然后重寫他的main方法, 之后就可以使用這個子類來進行相關操作了.
/*******************"WHOperation.h"*************************/
#import?@interface?WHOperation?:?NSOperation
@end
/*******************"WHOperation.m"*************************/
#import?"WHOperation.h"
@implementation?WHOperation
-?(void)main?{
????for(int?i?=?0;?i?<?3;?i++)?{
????????NSLog(@"NSOperation的子類WHOperation======%@",[NSThread?currentThread]);
????}
}
@end
/*****************回到主控制器使用WHOperation**********************/
-?(void)testWHOperation?{
????WHOperation?*operation?=?[[WHOperation?alloc]?init];
????[operation?start];
}
運行結果如下,依然是在主線程執行
SOperation的子類WHOperation======{number?=?1,?name?=?main}
NSOperation的子類WHOperation======{number?=?1,?name?=?main}
NSOperation的子類WHOperation======{number?=?1,?name?=?main}
所以NSOperation是需要配合NSOperationQueue來實現多線程的,下面來說一下隊列NSOperationQueue.
No.3: ?隊列NSOperationQueue
NSOperationQueue只有兩種隊列:主隊列、其他隊列.其他隊列包含了串行和并發.
主隊列的創建如下,主隊列上的任務是在主線程中執行的
NSOperationQueue?*mainQueue?=?[NSOperationQueue?mainQueue];
其他隊列(非主隊列)的創建如下,加入到"非主隊列的任務默認就是并發", 開啟多線程
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
注意:
1,非主隊列(其他隊列)可以實現串行或并行.
2,隊列NSOperationQueue有一個參數叫最大并發數: maxConcurrentOperationCount.
3,maxConcurrentOperationCount默認為-1,直接并發執行,所以加入到"非主隊列"中的任務默認就是并發,開啟多線程.
4,當maxConcurrentOperationCount為1時,則表示不開線程,也就是串行.
5,當maxConcurrentOperationCount大于1時,進行并發執行.
6,系統對對打并發數有一個限制,所以即使程序員把maxConcurrentOperationCount設置的很大,系統也會自動調整.所以把最大并發數設置的很大是沒有意義的.
No.4: NSOperation + NSOperationQueue
把任務加入隊列,這才是NSOperation的常規使用
◎addOperation添加任務到隊列
先創建好任務,然后用addOperation:方法把任務添加到隊列,示例代碼如下:
-?(void)testOperationQueue?{
????//?創建隊列,默認并發
????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
????//?創建操作,NSInvocationOperation
????NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperationAddOperation)?object:nil];
????//?創建操作,NSBlockOperation
????NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"addOperation把任務添加到隊列======%@",?[NSThread?currentThread]);
????????}
????}];
? ? [queue?addOperation:invocationOperation];
????[queue?addOperation:blockOperation];
}
-?(void)invocationOperationAddOperation?{
????NSLog(@"invocationOperation===aaddOperation把任務添加到隊列====%@",?[NSThread?currentThread]);
}
運行結果如下,可以看出,任務都是在子線程執行的,開啟了新線程!
invocationOperation===addOperation把任務添加到隊列===={number?=?4,?name?=?(null)}
addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}
addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}
addOperation把任務添加到隊列======{number?=?3,?name?=?(null)}
◎這是一個更方便的把任務添加到隊列的方法,直接把任務寫到block中,添加到任務中.
-?(void)testAddOperationWithBlock?{
????//?創建隊列,默認并發
????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
????//?添加操作到隊列
????[queue?addOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"addOperationWithBlock把任務添加到隊列======%@",?[NSThread?currentThread]);
????????}
????}];
}
運行結果如下,任務確實是在子線程中執行
addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務添加到隊列======{number?=?3,?name?=?(null)}
◎運用最大并發數實現串行
上面已經說過,可以運用隊列的的屬性maxConcurrentOperationCount(最大并發數)來實現串行,值需要把它設置為1就可以了,下面我們通過代碼驗證一下.
-?(void)testMaxConcurrentOperationCount?{
????//?創建隊列,默認并發
????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
????//?最大并發數為1,串行
????queue.maxConcurrentOperationCount?=?1;
????//?最大并發數為2,并發
//????queue.maxConcurrentOperationCount?=?2;
? ?//?添加操作到隊列
????[queue?addOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"addOperationWithBlock把任務添加到隊列1======%@",?[NSThread?currentThread]);
????????}
????}];
????//?添加操作到隊列
????[queue?addOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"addOperationWithBlock把任務添加到隊列2======%@",?[NSThread?currentThread]);
????????}
????}];
????//?添加操作到隊列
????[queue?addOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"addOperationWithBlock把任務添加到隊列3======%@",?[NSThread?currentThread]);
????????}
????}];
}
運行結果如下,當最大并發數為1的時候,雖然開啟了線程,但是任務是順序執行的,所以實現了串行
No.5: NSOperation的其他操作
◎取消隊列NSOperationQueue的所有操作, NSOperationQueue對象方法
-?(void)cancel
◎使隊列暫停或繼續
//?暫停隊列
[queue?setSuspended:YES];
◎判斷隊列是否暫停
-?(BOOL)isSuspended
暫停和取消不是立刻當前操作,而是等當前操作執行完之后不再進行新的操作.
No.6: NSOperation的操作依賴
NSOperation有一個非常好用的方法, 就是操作依賴.可以從字面意思理解:某一個操作(operetion2)依賴于另一個操作(operation1), 只有當operation1執行完畢,才能執行operation2,這時,就是才做依賴大顯身手的時候了.
-?(void)testAddDependency?{
? ? //?并發隊列
????NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
? ? //?操作1
????NSBlockOperation?*operation1?=?[NSBlockOperation?blockOperationWithBlock:^{
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"operation1======%@",?[NSThread??currentThread]);
????????}
????}];
? ? //?操作2
????NSBlockOperation?*operation2?=?[NSBlockOperation?blockOperationWithBlock:^{
????????NSLog(@"****operation2依賴于operation1,只有當operation1執行完畢,operation2才會執行****");
????????for(int?i?=?0;?i?<?3;?i++)?{
????????????NSLog(@"operation2======%@",?[NSThread??currentThread]);
????????}
????}];
? ? //?使操作2依賴于操作1
????[operation2?addDependency:operation1];
????//?把操作加入隊列
????[queue?addOperation:operation1];
????[queue?addOperation:operation2];
}
運行結果如下,操作2總是在操作1之后運行,成功驗證了上面的說法
operation1======{number?=?3,?name?=?(null)}
operation1======{number?=?3,?name?=?(null)}
operation1======{number?=?3,?name?=?(null)}
****operation2依賴于operation1,只有當operation1執行完畢,operation2才會執行****
operation2======{number?=?4,?name?=?(null)}
operation2======{number?=?4,?name?=?(null)}
operation2======{number?=?4,?name?=?(null)}