這里自己總結(jié)自己了解過的用過的一些多線程的方案,以及一些使用方法
進程和線程
說到多線程,往往在面試的時候會問一下進程和線程的區(qū)別,首先來說說進程,我們可以說進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,也可以說進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位。而線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它使比進程更小的能獨立運行的基本單位。簡而言之,線程就是把一個進程分為很多片,每一片都可以是一個獨立的流程。這已經(jīng)明顯不同于多進程了,進程是一個拷貝的流程,而線程只是把一條河流截成很多條小溪。它沒有拷貝這些額外的開銷,但是僅僅是現(xiàn)存的一條河流,就被多線程技術(shù)幾乎無開銷地轉(zhuǎn)成很多條小流程,它的偉大就在于它少之又少的系統(tǒng)開銷。雖然這些答案都可以在面試題的答案找到,但是當我們敘述給一個不是做編程的人解釋時還是很難讓人理解,這讓我想起家里人經(jīng)常問我軟件是什么,我說了一些自己的概念家里人也只是似懂非懂的感覺,這樣不如我們可以舉例子說明,比如打開QQ,微博他們就可以說是一個進程,而當我們用QQ聊天,聽音樂都可以說是在線程中進行的。
多線程
說完線程和進程,再說到多線程,我們知道當進入程序都會有一個主線程,我們也會稱為UI線程,我們往往會在這里進行UI更新操作(可以參考:刷新UI為什么在主線程里
),而當有一些耗時操作我們往往不會在這里進行,這時我們就要開啟多個線程
每條線程可以并行(同時)執(zhí)行不同的任務(wù),然而同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象,如果線程非常非常多,CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源,每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)
幾個專業(yè)術(shù)語:
同步和異步主要影響:能不能開啟新的線程
同步:在當前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行
串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)
多線程的作用:
1.網(wǎng)絡(luò)請求 2.圖片加載 3.文件處理 4.數(shù)據(jù)存儲 5.任務(wù)執(zhí)行
多線程的優(yōu)缺點:
優(yōu)點:1.簡化編程模型 2.更加輕量級 3.提高執(zhí)行效率 4.提高資源利用率
缺點:1.增加程序設(shè)計復雜行 2.占用內(nèi)存空間 3.增加CPU調(diào)度開銷
iOS在多線程中有4種方案
1.pThread 2.NSThread 3.GCD(GrandCentral Dispatch) 4.NSOperation
pThread
這個方案之前都不知道,最近才看到的,應(yīng)該很少人會用它,但看他的介紹在很多操作系統(tǒng)都可以用,移植性很強,但他是基于C語言的,用起來也很別扭,簡單看一下他在iOS中的使用
首先需要導入#import <pthread.h>頭文件然后創(chuàng)建線程
- (void)pThreadClick {
NSLog(@"主線程");
//極少用
pthread_t pthread;
pthread_create(&pthread, NULL, run, NULL);
}
void *run(void *data) {
NSLog(@"子線程");
for (int i = 1; i<10; i++) {
NSLog(@"%d",i);
sleep(1);
}
return NULL;
}
打印可見
2017-03-22 17:37:25.928 CJXProject[761:16992] 主線程
2017-03-22 17:37:25.980 CJXProject[761:17371] 子線程
2017-03-22 17:37:25.999 CJXProject[761:17371] 1
2017-03-22 17:37:27.111 CJXProject[761:17371] 2
2017-03-22 17:37:28.120 CJXProject[761:17371] 3
由于幾乎用不到就簡單看一下他的創(chuàng)建就好了
NSThread
這個方案是由蘋果封裝的,寫起來也很順眼,但是他的生命周期需要手動管理,我們往往會在調(diào)試的時候簡單使用,有興趣的可以先看一下官方文檔:NSThreadClass Reference
再看看如何創(chuàng)建使用的,他的創(chuàng)建有三種方式
1.直接init創(chuàng)建
//通過init方式創(chuàng)建
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(runThread) object:nil];
[thread1 start];
這種方式需要手動啟動
2.通過detachNewThreadSelector創(chuàng)建并且自動啟動
//通過detachNewThreadSelector
//[NSThread detachNewThreadSelector:@selector(runThread) toTarget:self withObject:nil];
3.通過performSelectorInBackground創(chuàng)建并且自動啟動
//通過performSelectorInBackground
//[self performSelectorInBackground:@selector(runThread) withObject:nil];
另外我們可以給線程起名字,可以取消
[thread1 setName:@"線程1"];//設(shè)置線程名字
[thread1 cancel];//取消
如果創(chuàng)建多個還可以設(shè)置優(yōu)先級
[thread1 setThreadPriority:0.7];//優(yōu)先級
其實這種方式我們用的時候也不多往往會在測試的時候用。
GCD
Grand Central Dispatch (GCD),它是為蘋果多核的并行運算提出的解決方案,所以會自動合理的利用更多的CPU內(nèi)核,更重要的是它會自動的管理線程的生命周期(創(chuàng)建線程,調(diào)度任務(wù),銷毀線程)。更重要的是,GCD是Apple官方推薦的方式,所以很多第三方組件都是用GCD來實現(xiàn)的,應(yīng)用使用GCD有時候就可以和第三方組件很好地配合,同時它使用的也是 C語言,不過由于使用了 Block,使得使用起來更加方便,而且靈活。
在開始使用GCD的時候,需要搞清楚任務(wù)和隊列這兩個概念。
任務(wù)有兩種執(zhí)行方式:
1.同步操作(sync),它會阻塞當前線程的操作并等待Block中的任務(wù)執(zhí)行完畢,然后當前線程才會繼續(xù)往下執(zhí)行。
2.異步操作(async),當前線程會直接的往下執(zhí)行,不會阻塞當前的線程。
隊列也有兩種隊列,串行隊列與并行隊列
串行隊列:遵照先進先出的原則,取出來一個執(zhí)行一個。
并行隊列:也會遵照先進先出的原則,但不同的是它會將取出來的任務(wù)放到別的線程執(zhí)行,然后再取出來一個放到另一個線程。
在GCD中還有一個特殊的隊列———主隊列,用來執(zhí)行主線程上的操作,dispatch_get_main_queue() 它是全局可用的串行隊列.
先看一個全局并行隊列:
//define DISPATCH_QUEUE_PRIORITY_HIGH 2 優(yōu)先級最高
//define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 優(yōu)先級中等
//define DISPATCH_QUEUE_PRIORITY_LOW (-2) 優(yōu)先級最低
NSLog(@"主線程");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"子線程");
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線程");
});
});
這是系統(tǒng)提供的一個并發(fā)隊列。
我們也可以自己創(chuàng)建隊列:
//其中第一個參數(shù)是標識符
//第二個參數(shù)用于表示創(chuàng)建的隊列是串行還是并行的,傳入DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊列。DISPATCH_QUEUE_CONCURRENT表示創(chuàng)建并行隊列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
然后就可以創(chuàng)建任務(wù):
dispatch_async(queue, ^{
NSLog(@"1");
sleep(2);
});
另外dispatch_group_async(隊列組)的使用,隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務(wù)都執(zhí)行完了,隊列組會通過dispatch_group_notify()方法獲得完成通知:
//1.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
//2.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//執(zhí)行任務(wù)
dispatch_group_async(group, queue, ^{
NSLog(@"start game1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end game1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start game2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end game2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"game over");
});
打印結(jié)果:
2017-03-22 20:45:21.514 CJXProject[2261:58481] start game1
2017-03-22 20:45:21.515 CJXProject[2261:58650] start game2
2017-03-22 20:45:23.571 CJXProject[2261:58481] end game1
2017-03-22 20:45:23.571 CJXProject[2261:58650] end game2
2017-03-22 20:45:23.574 CJXProject[2261:58650] game over
可以看到如我們預期的結(jié)果
如果我們有兩個網(wǎng)絡(luò)異步請求,當完成后需要得到通知我們就可以用到這個方法下面就做一下:
//模擬網(wǎng)絡(luò)異步請求
- (void)sendMsg1:(void(^)())block {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start game1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end game1");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
- (void)sendMsg2:(void(^)())block {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start game2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end game2");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
然后利用上面方法獲得通知:
dispatch_group_async(group, queue, ^{
[self sendMsg1:^{
NSLog(@"game1 done");
}];
});
dispatch_group_async(group, queue, ^{
[self sendMsg2:^{
NSLog(@"game2 done");
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"game over");
});
打印結(jié)果如下:
2017-03-22 20:59:00.950 CJXProject[2400:62715] start game1
2017-03-22 20:59:00.950 CJXProject[2400:62718] start game2
2017-03-22 20:59:00.956 CJXProject[2400:62714] game over
2017-03-22 20:59:03.002 CJXProject[2400:62715] end game1
2017-03-22 20:59:03.002 CJXProject[2400:62718] end game2
2017-03-22 20:59:03.005 CJXProject[2400:62605] game1 done
2017-03-22 20:59:03.007 CJXProject[2400:62605] game2 done
我們會發(fā)現(xiàn)和我們預期的不太一樣,當網(wǎng)絡(luò)請求還沒有完成我們就得到了通知,這是因為這兩個請求都是異步請求,當我們調(diào)用第一個請求的時候就是異步請求,所以她很快就完成了就不在持有這個請求了,這時我們就可以用dispatch_group_enter()來完成我們的需求:
dispatch_group_enter(group);
[self sendMsg1:^{
NSLog(@"game1 done");
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self sendMsg2:^{
NSLog(@"game2 done");
dispatch_group_leave(group);
}];
這時打印如下:
2017-03-22 21:07:43.803 CJXProject[2486:65358] start game1
2017-03-22 21:07:43.805 CJXProject[2486:65650] start game2
2017-03-22 21:07:45.817 CJXProject[2486:65358] end game1
2017-03-22 21:07:45.817 CJXProject[2486:65650] end game2
2017-03-22 21:07:45.820 CJXProject[2486:65268] game1 done
2017-03-22 21:07:45.823 CJXProject[2486:65268] game2 done
2017-03-22 21:07:45.826 CJXProject[2486:65650] game over
這樣就可以滿足我們的需求了。
另外如果我們只想讓一段代碼執(zhí)行一次,我們可以用dispatch_once(),我們還會常常在單例的時候用到它:
+ (instancetype)instance {
static dispatch_once_t onceToken;
static CJXSingle *single = nil;
dispatch_once(&onceToken, ^{
NSLog(@"init CJXSingle");
single = [[CJXSingle alloc]init];
});
return single;
}
上面就是一個常見單例的寫法,以后會單獨總結(jié)一下單例,這里就提一下。
另外還有一個常用的dispatch_after()也就是延遲執(zhí)行簡單實現(xiàn)一下:
NSLog(@"開始");
//延遲2秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"結(jié)束");
});
還有之前寫過的dispatch_source來創(chuàng)建計時器
另外還有不是很常用的:
dispatch_barrier_async():寫入操作會確保隊列前面的操作執(zhí)行完畢才開始,并會阻塞隊列中后來的操作.直到它執(zhí)行完成后才會執(zhí)行。
dispatch_apply():重復執(zhí)行某個任務(wù)。
NSOperation
NSOperation 是蘋果公司對 GCD 的封裝,NSOperation 只是一個抽象類,不能用于封裝任務(wù), 所以需要用它的子類NSInvocationOperation 和 NSBlockOperation來封裝,兩種方式?jīng)]有本質(zhì)的區(qū)別,但是后者使用Block的形式進行代碼組織,在使用的過程中更加方便。
可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 。
操作步驟也很好理解:
1.將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。
2.將此任務(wù)添加到一個 NSOperationQueue 對列中,線程就會依次啟動
先看一下NSInvocationOperation的創(chuàng)建:
NSInvocationOperation *invocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationClick) object:nil];
[invocation start];
在看NSBlockOperation:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
}];
[blockOperation start];
正如我們上面所說都差不多,但是這樣的任務(wù),默認會在當前線程執(zhí)行。這時我們就可以可以創(chuàng)建一個NSOperationQueue,然后將加入任務(wù)加入:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
[self.operQueue addOperation:blockOperation];
相比NSInvocationOperation推薦使用NSBlockOperation,因為它代碼簡單,同時由于閉包性使它沒有傳參問題,NSInvocationOperation在Swift中已不再支持。
另外我們可以自定義NSOperation,實現(xiàn)-(void)main函數(shù),新開一個線程,
或者重寫 start 方法,但需要手動管理當前狀態(tài)。
下面就簡單實現(xiàn)一下:
首先新建一個文件并且繼承與NSOperation,然后重寫一下main方法:
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.operName = name;
}
return self;
}
- (void)main {
for (int i = 0; i < 3; i++) {
NSLog(@"%@ %d",self.operName,i);
sleep(1);
}
}
怎么使用呢看下面:
CJXOperation *cjxOper1 = [[CJXOperation alloc]initWithName:@"oper1"];
CJXOperation *cjxOper2 = [[CJXOperation alloc]initWithName:@"oper2"];
CJXOperation *cjxOper3 = [[CJXOperation alloc]initWithName:@"oper3"];
[self.operQueue addOperation:cjxOper1];
[self.operQueue addOperation:cjxOper2];
[self.operQueue addOperation:cjxOper3];
這樣就可以了,另外我們可以設(shè)置最大并發(fā)數(shù):
[self.operQueue setMaxConcurrentOperationCount:3];表示最多3個
另外當NSOperation對象需要依賴于其它NSOperation對象完成時再操作,就可以通過addDependency方法添加一個或者多個依賴的對象,只有所有依賴的對象都已經(jīng)完成操作后,最開始的NSOperation對象才會開始執(zhí)行,通過removeDependency來刪除依賴對象。
//依賴
[cjxOper3 addDependency:cjxOper2];
[cjxOper2 addDependency:cjxOper1];
打印如下:
2017-03-22 21:55:10.609 CJXProject[2486:81331] oper1 0
2017-03-22 21:55:11.646 CJXProject[2486:81331] oper1 1
2017-03-22 21:55:12.714 CJXProject[2486:81331] oper1 2
2017-03-22 21:55:13.716 CJXProject[2486:81331] oper2 0
2017-03-22 21:55:14.755 CJXProject[2486:81331] oper2 1
2017-03-22 21:55:15.828 CJXProject[2486:81331] oper2 2
2017-03-22 21:55:16.897 CJXProject[2486:81331] oper3 0
2017-03-22 21:55:17.962 CJXProject[2486:81331] oper3 1
2017-03-22 21:55:18.978 CJXProject[2486:81331] oper3 2
可以看到3依賴2,2依賴1,注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
大體上的用法差不多都講完了,但是實際中的運用還是需要自己多實踐,最后總結(jié)一下:
pThread:極少使用
NSThread:適合輕量級多線程開發(fā),控制線程順序比較難,同時線程總數(shù)無法控制.
NSOperation:進行多線程開發(fā)可以控制線程總數(shù)及線程依賴關(guān)系,可以設(shè)置自身的優(yōu)先級,還可以判斷Operation當前的狀態(tài)(暫停,繼續(xù),取消)。
相比NSInvocationOperation推薦使用NSBlockOperation,代碼簡單,同時由于閉包性使它沒有傳參問題.
NSOperation是對GCD面向?qū)ο蟮腛bjC封裝,但是相比GCD基于C語言開發(fā),效率卻更高,建議如果任務(wù)之間有依賴關(guān)系或者想要監(jiān)聽任務(wù)完成狀態(tài)的情況下優(yōu)先選擇NSOperation否則使用GCD.
在GCD中串行隊列中的任務(wù)被安排到一個單一線程執(zhí)行(不是主線程),可以方便地控制執(zhí)行順序;并發(fā)隊列在多個線程中執(zhí)行(前提是使用異步方法),順序控制相對復雜,但是更高效.
在GCD中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當前隊列類型和執(zhí)行方法,只有隊列類型為并行隊列并且使用異步方法執(zhí)行時才能在多個線程中執(zhí)行(如果是并行隊列使用同步方法調(diào)用則會在主線程中執(zhí)行).
另外還有一些同步中加鎖的方法如:
@synchronized(self) { //需要執(zhí)行的代碼塊 }
常見的鎖以及性能:

另外關(guān)于線程鎖可以參考多線程開發(fā)之線程安全篇
這樣基本上就把多線程用到的方案都總結(jié)了一下,也借鑒了不少別的博客的東西,如果想更加深入的學習可以看看一些大牛的總結(jié),或者去看看官方文檔。
可以參考關(guān)于iOS多線程,你看我就夠了
iOS多線程總結(jié) 以及這篇文章下面的參考文章講的比較深,可以讓我們更好的了解多線程。