2017/02/20
今天的目標(biāo)只有一個:弄懂多線程(真心的)
一、認(rèn)識多線程
進(jìn)程:
在系統(tǒng)中正在運行的一個應(yīng)用程序;
線程:
一個進(jìn)程想要執(zhí)行任務(wù),必須得有線程(每一個進(jìn)程至少有一條線程),線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行。
多線程:
一個進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)(比如:進(jìn)程->車間,線程->車間工人)
(1)每一個程序都有一個主線程,程序啟動時創(chuàng)建(調(diào)用main來啟動)
(2)主線程的生命周期是和應(yīng)用程序綁定的,程序退出(結(jié)束)時,主線程也就停止了
(3)多線程技術(shù)表示,一個應(yīng)用程序有多個線程,使用多線程能提供CPU的使用率,防止主線程的堵塞
(4)任何有可能堵塞主線程的任務(wù)不要在主線程執(zhí)行(訪問網(wǎng)絡(luò))
二、多線程的原理
同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
思考:如果線程非常非常多,會發(fā)生什么情況?
CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
三、多線程的優(yōu)缺點
優(yōu)點:
- 能適當(dāng)提高程序的執(zhí)行效率
- 能適當(dāng)提高資源的利用率(CPU、內(nèi)存的利用率)
缺點:
- 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用了1M,子線程占用512K),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
- 線程越多,CPU在調(diào)度線程上的開銷就越大
- 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
四、多線程在iOS開發(fā)中的應(yīng)用
主線程:一個iOS程序運行后,默認(rèn)會開啟一條線程,稱為“主線程”或“UI線程”
主線程的主要作用:
- 顯示/刷新UI界面
- 處理UI事件(比如:點擊事件、滾動事件、拖拽事件等)
主線程的使用注意:別將比較耗時操作放到主線程中。
耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗
使用場合:
- 耗時操作,例如:網(wǎng)絡(luò)圖片、視頻、歌曲、書籍等資源下載
- 游戲中的聲音播放
五、iOS的三種多線程技術(shù)
1、NSThread:
優(yōu)點:NSThread比其他兩個輕量級,使用簡單
缺點:不能控制線程的執(zhí)行順序(需要自己管理線程的生命周期、線程同步、加鎖、睡眠以及喚醒等。線程同步對數(shù)據(jù)的加鎖對會有一定的系統(tǒng)開銷)【需要使用start方法,才能啟動實例化出來的線程、控制并發(fā)線程數(shù)、先后順序困難,例如:下載圖片(后臺線程)->濾鏡美化(后臺線程)->更新UI(主線程)】
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
for(int i=1; i<100; i++) {
NSLog(@"=====%@======%d",[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil].name,i);
if (i == 7) {
//創(chuàng)建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
//啟動新線程
[thread start];
/**
* 創(chuàng)建并啟動新線程
*/
//[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
/**
* 上面兩種方法本質(zhì)上都是將target對象的selector方法轉(zhuǎn)換為線程執(zhí)行體,其中selector方法最多可以接收一個參數(shù),而argument就是代表傳給selector方法的參數(shù)。這兩種創(chuàng)建新線程的方式并沒有明顯的區(qū)別,只是上面的是一個實例化方法,該方法返回一個thread對象,調(diào)用start方法開啟線程。下面的不會返回NSThread對象,因此這種方法會直接創(chuàng)建并啟動新線程。
*
*/
}
}
/**
* 每個線程執(zhí)行時都具有一定的優(yōu)先級,優(yōu)先級高的線程獲得較多的執(zhí)行機(jī)會,而優(yōu)先級低的則反之。每個子線程默認(rèn)的優(yōu)先級是0.5. NSThread 通過如下代碼演示優(yōu)先級。
*/
// NSLog(@"UI線程的優(yōu)先級為:%g",[NSThread threadPriority]);
// //創(chuàng)建第一個線程對象
// NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// //設(shè)置第一個線程對象的名字
// thread1.name = @"線程A";
// NSLog(@"線程A的優(yōu)先級為:%g",thread1.threadPriority);
// //設(shè)置使用最低優(yōu)先級
// thread1.threadPriority = 0.0;
// //創(chuàng)建第二個線程對象
// NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// //設(shè)置第二個線程對象的名字
// thread2.name = @"線程B";
// NSLog(@"線程B的優(yōu)先級為:%g",thread2.threadPriority);
// //設(shè)置使用最高優(yōu)先級
// thread2.threadPriority = 1.0;
// //啟動兩個線程
// [thread1 start];
// [thread2 start];
}
- (void)run {
for (int i=0; i<100; i++) {
// if ([NSThread currentThread].isCancelled) {
// //終止當(dāng)前正在執(zhí)行的線程
// [NSThread exit];
// }
NSLog(@"------%@-----%d",[NSThread currentThread].name,i);
//沒執(zhí)行一次,線程暫停0.5秒
// [NSThread sleepForTimeInterval:0.5];
//使當(dāng)前線程暫停一段時間,或者暫停到某個時刻
// + (void)sleepForTimeInterval:(NSTimeInterval)time;
// + (void)sleepUntilDate:(NSDate *)date;
}
}
//- (IBAction)cancelThread:(id)sender {
// //取消thread線程,調(diào)用該方法,調(diào)用該方法后,thread的isCancelled方法將會返回NO
// [thread cancel];
//}
@end
相關(guān)代碼下載:OC-NSThread
2.NSOperation和NSOperationQueue
NSOperation是一個抽象基類,基本沒有什么實際使用價值。我們使用最多的是系統(tǒng)封裝好的NSInvocationOperation和NSBlockOperation。
(1)NSOperation
NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο?,所以使用起來更好理解?大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 。操作步驟也很好理解:
- 將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。
- 將此任務(wù)添加到一個 NSOperationQueue 對象中。
NSOperation * operation = [[NSOperation alloc]init];
//開始執(zhí)行
[operation start];
//取消執(zhí)行
[operation cancel];
//執(zhí)行結(jié)束后調(diào)用的Block
[operation setCompletionBlock:^{
NSLog(@"執(zhí)行結(jié)束");
}];
NSInvocationOperation的使用方式和給Button添加事件比較相似,需要一個對象和一個Selector。使用方法非常簡單。
添加任務(wù)
值得說明的是,NSOperation 只是一個抽象類,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務(wù),它會 默認(rèn)在當(dāng)前隊列同步執(zhí)行。當(dāng)然你也可以在中途取消一個任務(wù),只需要調(diào)用其 cancel 方法即可。
//創(chuàng)建
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
//執(zhí)行
[invo start];
- (void)testNSOperation {
NSLog(@"我在第%@個線程",[NSThread currentThread]);
}
我們可以看到NSInvocationOperation其實是同步執(zhí)行的,因此單獨使用的話,這個東西也沒有什么卵用,它需要配合我們后面介紹的NSOperationQueue去使用才能實現(xiàn)多線程調(diào)用,所以這里我們只需要記住有這么一個東西就行了。參考文章:NSOperation
(2)NSBlockOperation
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務(wù)
[operation start];
3.GCD
Grand Central Dispatch,聽名字就霸氣。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是 c語言,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。
首先來了解兩個基本概念:任務(wù)和隊列
-
任務(wù):及操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間的區(qū)別是 是否會創(chuàng)建新的線程。
同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢! 如果是 同步(sync) 操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運行。 如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。 隊列:用于存放任務(wù)。一共有兩種隊列,串行隊列和并行隊列(先入先出)。
口訣:
- 同步不開異步開,串行開一條,并行開多條
(1)創(chuàng)建隊列
主隊列:
這是一個特殊的 串行隊列。什么是主隊列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。
dispatch_queue_t queue = ispatch_get_main_queue();
自己創(chuàng)建的隊列:
第一個參數(shù)是標(biāo)識符,第二個參數(shù)用來表示創(chuàng)建的對壘是串行還是并行。
//創(chuàng)建串行隊列 - DISPATCH_QUEUE_SERIAL或NULL
serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建并發(fā)隊列 - DISPATCH_QUEUE_CONCURRENT
concurrentQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_CONCURRENT);
全局并發(fā)隊列:
只要是并行任務(wù)一般都加入到這個隊列。這是系統(tǒng)提供的一個并發(fā)隊列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
(2)創(chuàng)建任務(wù)
同步任務(wù):會阻塞當(dāng)前線程(sync)
//將代碼塊以同步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該代碼塊
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
//將函數(shù)以同步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該函數(shù)
dispatch_sync_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)-
異步任務(wù):不會阻塞當(dāng)前線程 (async)
//將代碼塊以異步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該代碼塊 dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>) //將函數(shù)以異步方式提交給指定隊列,該隊列底層的線程池將負(fù)責(zé)執(zhí)行該函數(shù) dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
相關(guān)代碼下載:OC-GCD