作為一個開發(fā)人員, 有兩個詞無論是工作中還是面試中, 都會經常聽見, 被問及:"進程""線程"炼吴。 在開始了解多線程之前, 先來了解一下這二者的關系和區(qū)別: 簡單點說, 進程是一個有獨立地址空間的, 而線程只是一個進程中執(zhí)行任務的一個路徑, 這是二者有本質的區(qū)別, 可以說他們是不同的操作系統(tǒng)資源的管理方式粱锐。
做iOS開發(fā),很少會涉及到進程。我們一般會做的就是進程間通信剑鞍,從當前App跳轉到第三方App昨凡,例如“QQ”、“微信”或指定App蚁署。我們可能會在跳轉過程中傳遞一些參數便脊,在第三方App上做出相應的操作,這就是我目前涉及到的跟進程有關的開發(fā)光戈。這里我主要還是想講的是線程哪痰,因為,相比之下久妆,線程在我們開發(fā)過程中還是尤為重要的晌杰。
線程
可以說,一個進程是由一個或多個線程組成的筷弦,進程負責調度多條線程肋演, 真正執(zhí)行任務,負責代碼執(zhí)行的是線程烂琴。
在程序被啟動的時候爹殊,系統(tǒng)的主線程就被創(chuàng)建,直到程序完全退出监右,這個線程都會存在边灭。他用來執(zhí)行main函數。這時的主線程要完成所有的操作健盒,網絡請求绒瘦、UI展示、動畫等等扣癣。在只有主線程的情況下這些任務都要按順序一個一個執(zhí)行惰帽, 無法并發(fā)執(zhí)行。所以父虑,我們需要多線程來協(xié)助主線程完成這個并發(fā)執(zhí)行的任務该酗。
iOS多線程
多線程很好理解,有多條線程在執(zhí)行任務士嚎。
它其實是針對單核的CPU設計的呜魄, 因為單核的同一時間只能執(zhí)行一個任務, 但是我們可以利用多線程莱衩,讓CPU快速的在多個線程之間切換爵嗅,從而給我們一種多個任務在同時進行的錯覺。在多核的CPU中笨蚁, 是真實的達到了睹晒, 多個線程同時執(zhí)行任務趟庄。
1. 多線程優(yōu)缺點
優(yōu)點:① 適當的提高代碼執(zhí)行效率
? ? ? ? ? ?② 適當提高資源利用率
缺點:① 開啟線程過多,會占用大量內存空間伪很,所以要適度
? ? ? ? ? ?② 程序設計更復雜戚啥,需要考慮諸如線程安全的問題
2. iOS中的多線程
iOS中多線程主要有四種:NSThread、NSObject锉试、NSOperationQueue猫十、GCD
2.1 ?NSTread 線程類
優(yōu)點:1. 輕量級
? ? ? ? ? ? 2.可以快速創(chuàng)建子線程,并對子線程有控制權
缺點:1. 需要手動管理線程的生命周期
? ? ? ? ? ? 2.要手動開啟子線程键痛、基本信息要手動設置
2.1.1 NSTread的創(chuàng)建
NSTread的創(chuàng)建有三種方式炫彩, 后兩者相當于對第一種進行了封裝,我們不再可以設置線程的基本信息
基本的創(chuàng)建方式:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];
thread.name = @"線程類";
[thread start];
快速創(chuàng)建絮短,并自動開啟:
[NSThread detachNewThreadSelector:@selector(threadTask) toTarget:self withObject:nil];
隱式創(chuàng)建江兢,并自動開啟:
[self performSelectorInBackground:@selector(threadTask) withObject:nil];
這里面線程我讓他執(zhí)行threadTask里面的任務, 里面模擬了線程卡死代碼丁频,并看看當前是在哪執(zhí)任務
- (void)threadTask {
if ([NSThread isMainThread]) {
NSLog(@"當前線程 是 主線程杉允,是%@", [NSThread currentThread]);
} else {
NSLog(@"當前線程 非 主線程,是%@", [NSThread currentThread]);
}
NSInteger num = 0;
for (NSInteger i = 0; i < 1000000000; i++) {
num++;
}
NSLog(@"%ld", num);
[self performSelector:@selector(reloadUIView) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
}
這里面用到了幾個線程相關的方法:
isMainThread : 查看當前線程是否是主線程(0:非 ?/ ?1:是)
currentThread: 查單當前線程
mainThread: ? ? 獲得主線程
線程間通信:
在某個線程或主線程執(zhí)行某個任務
-(void)performSelector:(SEL)aSelectoron Thread:(NSThread*)thread withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
2.2 GCD
GCD是iOS開發(fā)中應用最為廣泛的多線程開發(fā)技術席里,使用得最簡單叔磷。GCD是蘋果公司自己開發(fā)的,基于C語言寫的奖磁,提供了非常強大的函數改基。
想學懂GCD,不得不先了解一下任務和隊列兩個核心概念
2.2.1 任務和隊列
一咖为、概念:
? ? ? ? 任務:執(zhí)行的操作秕狰,通常是我們代碼中的方法
? ? ? ? 隊列:可以理解為用來存放任務的列表
二、分類:
? ? ? ? 任務的種類(是否開辟新的線程):
? ? ? ? ? ? ? ? ? ? ?同步執(zhí)行(不開辟新線程)躁染,異步執(zhí)行(開辟新線程)
? ? ? ? 隊列的種類(任務的執(zhí)行方式):
? ? ? ? ? ? ? ? ? ? 并行隊列(多個任務可同時執(zhí)行)鸣哀,串行隊列(任務按順序一個一個執(zhí)行)
三、組合:
? ? ? ? 同步執(zhí)行+并行隊列
? ? ? ? 同步執(zhí)行+串行隊列
? ? ? ? 異步執(zhí)行+并行隊列
? ? ? ? 異步執(zhí)行+串行隊列
? ? 還有一種隊列叫主隊列吞彤,是串行我衬。加上這種隊列,又有兩種新組合方式
? ? ? ? 同步執(zhí)行+主隊列
? ? ? ? 異步執(zhí)行+主隊列
2.2.2 GCD的使用步驟
1->創(chuàng)建隊列:
? ? 并行隊列:
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
? ? 串行隊列:
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
? ? 主隊列:
dispatch_queue_t queue = dispatch_get_main_queue();
? ?全局隊列:
dispatch_queue_t globa = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2->創(chuàng)建任務:
? ? 同步執(zhí)行:
dispatch_sync(queue, ^{
});
? ? 異步執(zhí)行:
dispatch_async(queue, ^{
});
2.2.3 GCD死鎖
GCD死鎖的問題我也是繞了好久才繞明白饰恕。這里來列舉幾個會造成死鎖的案例:
案例1. 主隊列+同步執(zhí)行
// 同步主隊列
- (void)syncMainQueue {
NSLog(@"~~~1~~~");//任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"~~~2~~~");//任務2
});
NSLog(@"~~~3~~~");//任務3
}
執(zhí)行結果:
分析:進入到方法中挠羔,首先執(zhí)行任務1,接下來遇到同步線程的任務2埋嵌,隊列是先進先出的原則褥赊,由于三個任務全都是在主隊列上,同步的任務2實際上是插在了任務3的后面莉恼,但是任務3卻要等任務2執(zhí)行完畢之后才會執(zhí)行拌喉,這樣就形成了任務2和任務3互等的狀態(tài),無法往下執(zhí)行俐银,卡死在這里尿背。
解決:自定義串行隊列,配合同步線程
// 同步串行
- (void)syncSerial {
NSLog(@"~~~1~~~");
dispatch_sync(dispatch_queue_create("同步串行", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"~~~2~~~");
});
NSLog(@"~~~3~~~");
}
執(zhí)行結果:
分析:
主隊列也是串行隊列捶惜,同樣是同步線程田藐,但結果卻是不一樣的。造成執(zhí)行結果不同的唯一原因就是任務2是執(zhí)行在一個不同于任務1和任務3的隊列中吱七。
來看一下執(zhí)行順序汽久,首先執(zhí)行任務1,接著遇到同步線程的任務2踊餐,在任務2執(zhí)行完畢之后景醇,去執(zhí)行任務3。由于不是在一個隊列中吝岭,任務2是排在了自定義的隊列的第一個位置三痰, 不用等任務3執(zhí)行完就可以去執(zhí)行。窜管、
案例2:在自定義隊列中同步執(zhí)行
- (void)asyncAndSync {
NSLog(@"~~~1~~~");
dispatch_queue_t queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"~~~2~~~");
dispatch_sync(queue, ^{
NSLog(@"~~~3~~~");
});
NSLog(@"~~~4~~~");
});
NSLog(@"~~~5~~~");
}
執(zhí)行結果:
分析:
? 首先看看5個任務都是在哪個隊列執(zhí)行的 任務1-主隊列、任務2-自定義隊列幕帆、任務3-自定義隊列获搏、任務4-自定義隊列、任務5-主隊列失乾。
再來看看任務的執(zhí)行順序:首先執(zhí)行任務1常熙;接著遇到異步任務,暫時跳過仗扬,執(zhí)行任務5症概;接著進入到異步任務內,執(zhí)行任務2早芭;接下來遇到同步任務3彼城,但是任務3,任務2退个,任務4都是執(zhí)行在自定義的隊列里募壕,所以他排在了任務4后,但是任務4還在等同步隊列執(zhí)行完才執(zhí)行语盈, 所以兩者進入了互等狀態(tài)舱馅,卡死在這里。
2.2.4 GCD對比
2.3 NSOperation 操作類
單獨的NSOperation對象是不能進行多線程編輯的刀荒,它需要配合NSOperationQueue(操作隊列)來實現多線程
首先我們應該知道代嗤,NSOperation是一個抽象類棘钞, 它本事并不具備封裝操作的能力, 所以我們必須創(chuàng)建NSOperation的子類
2.3.1 創(chuàng)建NSOperation
NSOperation的子類有三種:NSInvocationOperation干毅、NSBlockOperation和自定義的繼承于NSOperation的類宜猜。
2.3.2 NSOperation對象的創(chuàng)建與使用
1-> NSInvocationOperation
- (void)invocationOperation {
NSInvocationOperation *operation_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];
[operation_1 start];
NSInvocationOperation *operation_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];
[operation_2 start];
}
2-> NSBlockOperation
? ? ? ?這里創(chuàng)建NSBlockOperation對象的方法:
? ? ? ? ? ? ? +(id)blockOperationWithBlock:(void(^)(void))block;
? ? ? ?添加操作:
? ? ? ? ? ? ?-(void)addExecutionBlock:(void(^)(void))block;
- (void)blockOperation {
NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"~~~1.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~2.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~3.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~4.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~5.%@~~~", [NSThread currentThread]);
}];
[operation_1 start];
}
執(zhí)行結果:
3->自定義類
自定義的繼承于NSOperation的類需要重寫main方法
2.3.3 NSOperationQueue
// 創(chuàng)建隊列
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
// 設置最大并發(fā)數(同時可處理的線程的個數)
[queue setMaxConcurrentOperationCount:2];
// 創(chuàng)建任務
MyOperation *op1 = [[MyOperation alloc] init];
MyOperation *op2 = [[MyOperation alloc] init];
// 添加到隊列
[queue addOperation:op1];
[queue addOperation:op2];