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