目錄:
<a href="#(一)">(一)線程與進(jìn)程之間的區(qū)別</a>
<a href="#(二)">(二)為什么需要學(xué)習(xí)多線程</a>
<a href="#(三)">(三)多線程任務(wù)執(zhí)行方式</a>
<a href="#(四)">(四)多線程執(zhí)行的原理</a>
<a href="#(五)">(五)多線程的優(yōu)缺點(diǎn)</a>
<a href="#(六)">(六)在iOS開發(fā)中的多線程實(shí)現(xiàn)技術(shù)方案</a>
<li><a href="#(A)">(A)PThread</a>
<li><a href="#(B)">(B)NSThread</a>
<li><a href="#(C)">(C)GCD</a>
<ul><a href="#(1)">(1) dispatch_get_global_queue 探究</a></ul>
<ul><a href="#(2)">(2)dispatch_group的探索</a></ul>
<ul><a href="#(3)">(3)dispatch_once探究</a></ul>
<ul><a href="#(4)">(4)dispatch_after探究</a></ul></li>
<li><a href="#(D)">(D)NSOperation</a>
<ul><a href="#(D1)">(1)NSInvocationOperation探究</a></ul>
<ul><a href="#(D2)">(2)NSBlockOperation探究</a></ul>
<ul><a href="#(D3)">(3)NSOperationQueue探究</a></ul>
<ul><a href="#(D4)">(4)自定義NSOperation子類探究</a>
<ul><a href="#(D4.1)">(4.1)maxConcurrentOperationCount 屬性</a></ul>
<ul><a href="#(D4.2)">(4.2)addDependency 方法添加依賴:</a></ul></ul>
<a href="#(七)">(七)線程鎖相關(guān)</a></br>
<a href="#(八)">(八)總結(jié)</a></br></br>
【文章篇幅有點(diǎn)偏多,有興趣的可以繼續(xù)讀下去】
一般說(shuō)到線程,那么首先要區(qū)分一下線程與進(jìn)程,首先來(lái)簡(jiǎn)單的區(qū)分一下兩者的關(guān)系
進(jìn)程:是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。
線程:是指進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,也是進(jìn)程內(nèi)的可調(diào)度實(shí)體。是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,他是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(寄存器,棧,程序計(jì)數(shù)器),但是它可與同一個(gè)進(jìn)程的其他線程共享進(jìn)程所擁有的全部資源
<a name="(一)">(一)線程與進(jìn)程之間的區(qū)別</a>
(1)地址空間:進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,進(jìn)程至少包含一個(gè)線程,他們共享進(jìn)程的地址空間,而進(jìn)程有自己獨(dú)立的地址空間
(2)資源擁有:進(jìn)程是資源分配和擁有的單位,同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程資源 【進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程?!?/code>
(3)線程是處理器調(diào)度的基本單位,但進(jìn)程不是
(4)二者皆可并發(fā)執(zhí)行
<a name="(二)">(二)為什么需要學(xué)習(xí)多線程</a>
因?yàn)樵诔绦蜻\(yùn)行中,對(duì)于網(wǎng)絡(luò)請(qǐng)求、圖片加載、文件處理、數(shù)據(jù)存儲(chǔ)、任務(wù)執(zhí)行等等這些操作都需要放到異步線程中進(jìn)行處理,這也顯得多線程的重要性
<a name="(三)">(三)多線程任務(wù)執(zhí)行方式</a>
主要分為兩種:串行和并行
串行:(簡(jiǎn)易)指的是多個(gè)任務(wù)按照一定順序執(zhí)行(任務(wù)執(zhí)行有順序依賴關(guān)系),例如有三個(gè)任務(wù)執(zhí)行,并且需要的執(zhí)行順序是 線程1->線程2->線程3,那么這三個(gè)任務(wù)執(zhí)行完畢所需的時(shí)間就是 t1 + t2 + t3

并行:(簡(jiǎn)易)并發(fā)執(zhí)行多個(gè)任務(wù)(任務(wù)執(zhí)行沒(méi)有順序依賴關(guān)系),例如有三個(gè)任務(wù)執(zhí)行,假設(shè)任務(wù)2的執(zhí)行時(shí)間最長(zhǎng),那么這三個(gè)任務(wù)執(zhí)行完畢所需的時(shí)間就是 t2

有一點(diǎn)需要明白的是:兩種任務(wù)執(zhí)行方式并沒(méi)有好壞之分的,只是根據(jù)自己的需求進(jìn)行選擇使用
并行執(zhí)行還是串行執(zhí)行
<a name="(四)">(四)多線程執(zhí)行的原理</a>

在單核操作系統(tǒng)的多線程執(zhí)行,其實(shí)是采用時(shí)間片輪轉(zhuǎn)調(diào)度來(lái)實(shí)現(xiàn)的,操作系統(tǒng)會(huì)采用時(shí)間片輪轉(zhuǎn)調(diào)度的方式為每一個(gè)線程間接性的分配時(shí)間執(zhí)行任務(wù),當(dāng)線程1執(zhí)行的時(shí)候,線程2就處于阻塞或者空閑的狀態(tài),當(dāng)時(shí)間片執(zhí)行到線程2時(shí),執(zhí)行循序有會(huì)反過(guò)來(lái),所以對(duì)于單核操作系統(tǒng)來(lái)說(shuō)的多線程執(zhí)行方式就是:
宏觀上的并行,微觀上的串行
對(duì)于多核操作系統(tǒng)來(lái)說(shuō),就可以說(shuō)是真正意義上的并行執(zhí)行,因?yàn)槊恳粋€(gè)處理器都會(huì)按照時(shí)間片輪轉(zhuǎn)的方式執(zhí)行任務(wù),多個(gè)核心處理器就可以實(shí)現(xiàn)多個(gè)任務(wù)同時(shí)執(zhí)行的效果
<a name="(五)">(五)多線程的優(yōu)缺點(diǎn)</a>
優(yōu)點(diǎn):
(1)簡(jiǎn)化了變成模型:可以將原本放在一個(gè)線程中執(zhí)行的一些耗時(shí)或較為大的任務(wù)進(jìn)行分割到多個(gè)線程中執(zhí)行
(2)更加輕量級(jí)
(3)提高了執(zhí)行效率
(4)提高資源利用率
缺點(diǎn):
(1)增加了程序設(shè)計(jì)的復(fù)雜性:因?yàn)樵诙嗑€程中我們需要處理的最大問(wèn)題就是資源共享問(wèn)題和數(shù)據(jù)讀寫問(wèn)題,如果兩個(gè)線程同時(shí)修改同一個(gè)數(shù)據(jù)或?qū)傩?,就?huì)出現(xiàn)問(wèn)題,所以在一定程度上增加了程序設(shè)計(jì)的復(fù)雜性
(2)占用內(nèi)存空間:因?yàn)槿绻环謭?chǎng)合隨意使用多線程的時(shí)候,會(huì)導(dǎo)致程序內(nèi)存的增加,這對(duì)客戶端開發(fā)來(lái)說(shuō)是一個(gè)絕對(duì)不能忽視的問(wèn)題,所以我們需要適度、合理的使用多線程開發(fā)
(3)增加CPU調(diào)度開銷:因?yàn)樵诙嗑€程執(zhí)行任務(wù)時(shí),是使用時(shí)間片調(diào)度的方式進(jìn)行的,頻繁的切換時(shí)間片,必然會(huì)增大CPU的調(diào)度開銷
<a name="(六)">(六)在iOS開發(fā)中的多線程實(shí)現(xiàn)技術(shù)方案</a>

下面就通過(guò)Demo對(duì)這四種方式進(jìn)行一一解釋
<a name="(A)">(A)PThread</a>
#pragma mark ---- 測(cè)試 pThread
/**
測(cè)試 pThread
*/
- (IBAction)runPThread:(id)sender {
NSLog(@"我是在主線程中執(zhí)行\(zhòng)n\n");
pthread_t pthread;
pthread_create(&pthread, NULL, run, NULL);
}
/**
C語(yǔ)言函數(shù)
*/
void * run(void * data){
NSLog(@"我是在子線程中執(zhí)行\(zhòng)n\n");
for (int i = 1; i <= 10; i++) {
NSLog(@"%d \n\n",i);
sleep(1);
}
return NULL;
}
從代碼中可以看出pThread的創(chuàng)建執(zhí)行其實(shí)也是比較簡(jiǎn)單的,不過(guò)實(shí)現(xiàn)過(guò)程是通過(guò)C語(yǔ)言進(jìn)行的,從創(chuàng)建方法pthread_create(<#pthread_t _Nullable *restrict _Nonnull#>, <#const pthread_attr_t *restrict _Nullable#>, <#void * _Nullable (* _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)可以看出,第一個(gè)參數(shù)是需要一個(gè)pthread 對(duì)象指針,第三個(gè)是需要一個(gè)C語(yǔ)言函數(shù)方法(就當(dāng)于OC中綁定的執(zhí)行方法),至于第二個(gè)和第四個(gè)參數(shù),暫時(shí)沒(méi)有什么用(其實(shí)偶也不曉得什么作用)可以直接傳入NULL

從打印結(jié)果中可以看出和我們預(yù)期的結(jié)果相同,成功的開啟了一個(gè)子線程
細(xì)心地童鞋可以會(huì)發(fā)現(xiàn)圖中紅色箭頭指向的兩組數(shù)字,其實(shí)在我們的輸出控制臺(tái)輸出的都有這兩組數(shù)字,但是很多朋友可能并沒(méi)有注意過(guò)這些,也不知道是什么意思?!

其實(shí)第一組數(shù)字
24592表示的是當(dāng)前程序所處的 進(jìn)程 ID,而第二組數(shù)字1923132則表示當(dāng)前所處的線程 ID,所以我們就可以通過(guò)線程ID進(jìn)行判斷是否成功開啟了一個(gè)子線程
<a name="(B)">(B)NSThread</a>
NSThread可能是我們?cè)贠C開發(fā)中接觸最早的多線程實(shí)現(xiàn)技術(shù),而且NSThread的實(shí)現(xiàn)多線程的方式也有三種,下面就通過(guò)代碼做解釋
NSThread的實(shí)現(xiàn)方式一:
#pragma mark ---- 測(cè)試 NSThread
/**
測(cè)試 NSThread
*/
- (IBAction)runNSThread:(id)sender {
NSLog(@"我是在主線程中執(zhí)行\(zhòng)n");
/*
創(chuàng)建方式 1 :通過(guò) alloc initWithTarget 進(jìn)行創(chuàng)建
好處:可以通過(guò) NSThread 對(duì)象設(shè)置一些線程屬性;例如線程 名字
*/
NSThread * thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(runThread1) object:nil];
[thread1 setName:@"Name_Thread1"];// 設(shè)置線程名字
[thread1 setThreadPriority:0.1];// 設(shè)置線程優(yōu)先級(jí)
[thread1 start];
NSThread * thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(runThread1) object:nil];
[thread2 setName:@"Name_Thread2"];// 設(shè)置線程名字
[thread2 setThreadPriority:0.5];// 設(shè)置線程優(yōu)先級(jí)
[thread2 start];
}
/// 方式一
-(void)runThread1{
for (int i = 11; i <= 20; i++) {
NSLog(@"%d -- %@",i,[NSThread currentThread].name);
sleep(1);
if (i == 20) {
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
}
}
}
-(void)runMainThread{
NSLog(@" 回調(diào)主線程");
}

方式一是通過(guò) alloc initWithTarget 進(jìn)行創(chuàng)建,這種方式的好處是可以通過(guò) NSThread 對(duì)象設(shè)置一些線程屬性;例如線程 名字,從控制臺(tái)信息可以看出來(lái),當(dāng)設(shè)置了不同的NSThread對(duì)象的優(yōu)先級(jí)屬性,可以控制其執(zhí)行的順序,優(yōu)先級(jí)越高,越先執(zhí)行;而設(shè)置名字屬性后,可以通過(guò)調(diào)試監(jiān)控當(dāng)前所處線程,便于問(wèn)題分析
NSThread的實(shí)現(xiàn)方式二:
// 創(chuàng)建方式 2 :通過(guò) detachNewThreadSelector 方式創(chuàng)建并執(zhí)行線程
[NSThread detachNewThreadSelector:@selector(runThread2) toTarget:self withObject:nil];
/// 方式二綁定方法
-(void)runThread2{
NSLog(@"我是在子線程中執(zhí)行\(zhòng)n\n");
for (int i = 11; i <= 20; i++) {
NSLog(@"%d \n\n",i);
sleep(1);
if (i == 20) {
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
}
}
}

NSThread的實(shí)現(xiàn)方式三:
// 創(chuàng)建方式 3 :通過(guò) performSelectorInBackground 方式創(chuàng)建并執(zhí)行線程
[self performSelectorInBackground:@selector(runThread3) withObject:nil];
/// 方式三綁定方法
/// 方式三
-(void)runThread3{
NSLog(@"我是在子線程中執(zhí)行\(zhòng)n");
for (int i = 21; i <= 30; i++) {
NSLog(@"%d \n",i);
sleep(1);
if (i == 30) {
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
}
}
}

在三組控制臺(tái)輸出結(jié)果對(duì)比可以發(fā)現(xiàn),三種方式都能達(dá)到預(yù)期效果
<a name="(C)">(C)GCD</a>
關(guān)于GCD可能也是我們開發(fā)過(guò)程中使用最多的一種方式,但是大多數(shù)可能都只是只知其一,不知其二,會(huì)用其中一兩個(gè)方法,就覺(jué)得會(huì)用GCD啦,其實(shí)這是遠(yuǎn)遠(yuǎn)不夠的,那我們就一起來(lái)探討一下GCD的強(qiáng)大之處:
1、GCD的描述:
純C語(yǔ)言開發(fā),是蘋果公司為多核的并行運(yùn)算提出的解決方案,會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核),可以自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)。
2、GCD的兩個(gè)核心
2.1 任務(wù)
執(zhí)行的操作,在GCD中,任務(wù)是通過(guò) block來(lái)封裝的。并且任務(wù)的block沒(méi)有參數(shù)也沒(méi)有返回值。
2.2 隊(duì)列存放任務(wù)包括
串行隊(duì)列
并發(fā)隊(duì)列
主隊(duì)列
全局隊(duì)列
首先還是像上面一樣通過(guò)簡(jiǎn)單Demo看看它的基本功能:
#pragma mark ---- 測(cè)試 GCD
- (IBAction)runGCD:(id)sender {
NSLog(@"執(zhí)行 GCD");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@" start tast 1");
// 執(zhí)行耗時(shí)任務(wù)
[NSThread sleepForTimeInterval:3];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回調(diào)主線程刷新UI");
});
});
}
打印結(jié)果:

同樣能夠?qū)崿F(xiàn)這樣的功能,接下來(lái)就一步步的來(lái)具體分析GCD:
<a name="(1)">(1) dispatch_get_global_queue 探究:</a>

由打印信息可以看出,三個(gè)線程是同一時(shí)間開始執(zhí)行,同一時(shí)間結(jié)束執(zhí)行的,
這就說(shuō)明GCD中的dispatch_get_global_queue是全局并發(fā)的隊(duì)列
/*
第一個(gè)參數(shù)設(shè)置隊(duì)列 優(yōu)先級(jí),這樣可以控制任務(wù)開始執(zhí)行的先后順序,第二個(gè)參數(shù)沒(méi)有用到
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級(jí)
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級(jí)
*/
dispatch_get_global_queue(long identifier, unsigned long flags)

這樣可以根據(jù)自己的需要控制任務(wù)開始執(zhí)行的先后順序。但是如果想讓任務(wù)結(jié)束的時(shí)間也按照我們的意愿進(jìn)行,那就需要使用到串行隊(duì)列,我們可以根據(jù)需要自定義串行隊(duì)列或者并行隊(duì)列
/*
自定義隊(duì)列 queue
參數(shù)一:隊(duì)列標(biāo)識(shí)符
參數(shù)二:定義隊(duì)列是串行還是并行,NULL(默認(rèn))或者 DISPATCH_QUEUE_SERIAL 為串行,DISPATCH_QUEUE_CONCURRENT 表示并行隊(duì)列
*/
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)


由上面的這列張圖所示的輸出信息可以清楚的看出自定義串行隊(duì)列和并行隊(duì)列的區(qū)別。
<a name="(2)">(2)dispatch_group的探索:</a>
隊(duì)列組就是可以對(duì)多個(gè)隊(duì)列進(jìn)行操作的一個(gè)組,在隊(duì)列組中可以對(duì)不同隊(duì)列進(jìn)行操作監(jiān)聽結(jié)果等等,首先來(lái)說(shuō)一下隊(duì)列組的監(jiān)聽方法dispatch_group_notify的用法:
NSLog(@"執(zhí)行GCD");
dispatch_queue_t queue = dispatch_queue_create("GCD_Group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 3");
});
/// group 組的監(jiān)聽通知,所有task結(jié)束之后回調(diào)
dispatch_group_notify(group, queue, ^{
NSLog(@"All tasks over");
/*
并非另外開辟一個(gè)新線程,而是在三個(gè)任務(wù)中的其中一個(gè)子線程進(jìn)行回調(diào),
所以如果需要進(jìn)行刷新 UI的話,需要回調(diào)到主線程處理
*/
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回調(diào)主線程刷新UI");
});
});
運(yùn)行結(jié)果為:

由打印結(jié)果可以看出,將三個(gè)并行隊(duì)列放入到隊(duì)列組中時(shí),使用
dispatch_group_notify方法可以對(duì)隊(duì)列執(zhí)行的結(jié)果進(jìn)行監(jiān)聽,而且這個(gè)監(jiān)聽回調(diào)只有在隊(duì)列組中的三個(gè)異步線程都處理完成時(shí)才會(huì)執(zhí)行回調(diào),這在我們實(shí)際開發(fā)過(guò)程中也是一項(xiàng)非常常見的需求!不少童鞋看到這里可能覺(jué)得會(huì)用
dispatch_group_notify隊(duì)列組了,但是還有一種更常見的情況是需要倍加注意的,具體請(qǐng)見下列demo:

dispatch_group_notify,現(xiàn)在打印的結(jié)果卻是:任務(wù)1和任務(wù)2開始之后,隊(duì)列組就回調(diào)了dispatch_group_notify,頓時(shí)感覺(jué)自己使用了一個(gè)假的dispatch_group隊(duì)列組......其實(shí)這才是實(shí)際開發(fā)中最常遇到的場(chǎng)景:當(dāng)我們執(zhí)行的任務(wù)中調(diào)起了一個(gè)異步的API請(qǐng)求,那么只要這個(gè)異步請(qǐng)求開始發(fā)送之后,
dispatch_group_async就會(huì)認(rèn)為當(dāng)前任務(wù)已經(jīng)處理完畢,之后這個(gè)異步API處理的事情就不在我的監(jiān)控范圍之內(nèi)啦,所以就造成了這種打印結(jié)果的出現(xiàn)。那么面對(duì)這種情況,需要如何處理才能正確監(jiān)聽任務(wù)執(zhí)行結(jié)果呢?如下處理:


dispatch_group_enter和dispatch_group_leave便可對(duì)隊(duì)列組中的不同異步請(qǐng)求進(jìn)行監(jiān)聽,最終執(zhí)行回調(diào)dispatch_group_async方法。但是有兩點(diǎn)需要注意的是:(1)dispatch_group_enter和dispatch_group_leave的使用必須是成對(duì)出現(xiàn);(2)dispatch_group_leave必須放在任務(wù)的最后一句執(zhí)行當(dāng)然GCD的隊(duì)列組的奧秘遠(yuǎn)不止這些,目前只是列出了常用的集中以及使用場(chǎng)景,如果感興趣的大神可以繼續(xù)參考官方API研究!
<a name="(3)">(3)dispatch_once探究:</a>
dispatch_once是GCD提供的一種創(chuàng)建單例的API方法,因?yàn)樵谖覀兊膶?shí)際開發(fā)過(guò)程中,單例也是非常常用的一個(gè)場(chǎng)景,例如全局的數(shù)據(jù)、公共對(duì)象等等這些都需要通過(guò)單例進(jìn)行處理,而單例顧名思義,就是在工程的整個(gè)運(yùn)行過(guò)程中只會(huì)創(chuàng)建一次,然后會(huì)存在于內(nèi)存中。例如:
/// 單例的創(chuàng)建
+(instancetype)instance {
static dispatch_once_t onceToken;
static SingleTest * inst = nil;
dispatch_once(&onceToken, ^{
NSLog(@"初始化單例對(duì)象");
inst = [[SingleTest alloc]init];
});
return inst;
}
調(diào)用方法

從輸出也可以看出來(lái),只有當(dāng)?shù)谝淮吸c(diǎn)擊方法時(shí)會(huì)創(chuàng)建對(duì)象,之后點(diǎn)擊方法時(shí)將不會(huì)在次創(chuàng)建對(duì)象,所有打印的對(duì)象內(nèi)存地址都相同,證明是同一個(gè)單例對(duì)象
<a name="(4)">(4)dispatch_after探究:</a>

這是GCD中提供的一個(gè)延時(shí)操作API,使用起來(lái)很簡(jiǎn)單,但是在個(gè)方法會(huì)存在一個(gè)陷阱,當(dāng)延時(shí)操作開始之后將無(wú)法取消,所以當(dāng)在一個(gè)界面執(zhí)行延時(shí)操作時(shí),界面消失之后仍然會(huì)執(zhí)行操作,這樣就可能造成程序crash,所以使用的時(shí)候需要多加注意。以上就是對(duì)GCD進(jìn)行的一個(gè)簡(jiǎn)單了解
<a name="(D)">(D)NSOperation</a>
1、NSOperation簡(jiǎn)介
1.1 NSOperation與GCD的區(qū)別:
OC語(yǔ)言中基于 GCD 的面向?qū)ο蟮姆庋b;
使用起來(lái)比 GCD 更加簡(jiǎn)單;
提供了一些用 GCD 不好實(shí)現(xiàn)的功能;
蘋果推薦使用,使用 NSOperation 程序員不用關(guān)心線程的生命周期
1.2 NSOperation的特點(diǎn)
NSOperation 是一個(gè)抽象類,抽象類不能直接使用,必須使用它的子類
抽象類的用處是定義子類共有的屬性和方法
2、核心概念
將操作添加到隊(duì)列,異步執(zhí)行。相對(duì)于GCD創(chuàng)建任務(wù),將任務(wù)添加到隊(duì)列。
將NSOperation添加到NSOperationQueue就可以實(shí)現(xiàn)多線程編程
3、操作步驟
先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
然后將NSOperation對(duì)象添加到NSOperationQueue中
系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來(lái)
將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
<a name="(D1)">(1)NSInvocationOperation探究</a>

1、從打印輸出的線程 ID可以看出:NSInvocationOperation的輸出操作和
[invocationOper start]是在同一個(gè)線程中,即[invocationOper start]如果在主線程中發(fā)起,則NSInvocationOperation的輸出操作也在主線程;[invocationOper start]如果在子線程中發(fā)起,則NSInvocationOperation的輸出操作也在相應(yīng)的子線程中;NSInvocationOperation不會(huì)開啟一個(gè)新線程2、有打印輸出的順序可以看出:NSInvocationOperation的執(zhí)行是同步執(zhí)行的
<a name="(D2)">(2)NSBlockOperation探究</a>

NSBlockOperation打印的結(jié)果和上面NSInvocationOperation如出一轍,一毛一樣,這也就證明了系統(tǒng)提供的兩個(gè)子類NSInvocationOperation ``NSBlockOperation都是同步執(zhí)行的
<a name="(D3)">(3)NSOperationQueue探究</a>
首先來(lái)看一下其相關(guān)的概念及關(guān)鍵詞


在使用
NSOperationQueue對(duì)象addOperation的方式執(zhí)行任務(wù),而不是通過(guò) start執(zhí)行,輸出打印的結(jié)果會(huì)有明顯的不同1、
NSOperationQueue執(zhí)行任務(wù)會(huì)開啟一個(gè)新線程2、
NSOperationQueue執(zhí)行任務(wù)是一個(gè)異步的操作過(guò)程
<a name="(D4)">(4)自定義NSOperation子類探究 </a>
首先我們可以創(chuàng)建一個(gè)NSOperation的子類,并且重寫main方法,在代碼中是一個(gè)什么效果呢?


<a name="(D4.1)">(4.1)maxConcurrentOperationCount 屬性: </a>
未設(shè)置并發(fā)數(shù)時(shí),默認(rèn)所有任務(wù)同時(shí)并發(fā)執(zhí)行

當(dāng)設(shè)置了最大并發(fā)數(shù)為 2 時(shí),如下圖可以看出
NSOperationQueue同時(shí)執(zhí)行的任務(wù)數(shù)也為兩個(gè),當(dāng)前兩個(gè)任務(wù)執(zhí)行完畢之后才繼續(xù)執(zhí)行后面的任務(wù)
<a name="(D4.2)">(4.2)addDependency 方法添加依賴: </a>
一般在我們的實(shí)際開發(fā)過(guò)程中,會(huì)遇到異步任務(wù)一需要等待異步任務(wù)二完成之后才能執(zhí)行,這種情況下可以就會(huì)想到使用多線程的依賴進(jìn)行實(shí)現(xiàn)(當(dāng)然使用上面說(shuō)的GCD也可以),那下面就說(shuō)一下 NSOperation中的addDependency方法:

[customA addDependency:customC];
[customC addDependency:customB];
[customB addDependency:customD];
這三句表示的依賴關(guān)系是:customA -> customC -> customB -> customD``customA任務(wù)需要在customC任務(wù)執(zhí)行之后才能執(zhí)行;customC任務(wù)需要在customB任務(wù)執(zhí)行之后才能執(zhí)行;customB任務(wù)需要在customD任務(wù)執(zhí)行之后才能執(zhí)行【注意:customD任務(wù)不能再依賴于customA任務(wù),否則就會(huì)造成死鎖】;使用看到了最終控制臺(tái)輸出的順序效果。
當(dāng)然上面這是一種理想的狀態(tài),如果出現(xiàn)了下面這種 “ 變態(tài) ” 情況,這種依賴關(guān)系還可靠嗎???
當(dāng)自定NSOperation的自定義類中的main方法執(zhí)行的是一個(gè)異步任務(wù):
-(void)main{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
if (self.cancelled) {
return ;
}
NSLog(@"---%@",self.operName);
});
}
輸出的打印順序如下:

其實(shí)是因?yàn)樽远x的NSOperation子類
main方法中,因?yàn)?code>main方法執(zhí)行的是一個(gè)異步任務(wù),當(dāng)任務(wù)開始執(zhí)行之后,NSOperation子類就默認(rèn)依賴任務(wù)完成,而無(wú)法監(jiān)聽到這個(gè)異步任務(wù)執(zhí)行結(jié)束。但是這種場(chǎng)景也是實(shí)際開發(fā)中經(jīng)常用到的,所以要怎樣處理呢?解決方法就是使用
NSRunLoop進(jìn)行解決:
while (!self.over && !self.cancelled) {
[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
在代碼中的這句作用就是讓當(dāng)前的 RunLoop 在main方法中等待異步任務(wù)的結(jié)束,這樣一來(lái)問(wèn)題就完美解決啦,下面看一下輸出效果:

輸出的結(jié)果符合自己設(shè)置的依賴預(yù)期,問(wèn)題完美解決。
<a name="(七)">(七)線程鎖相關(guān)</a>
多線程在開發(fā)中給我們帶來(lái)了很多遍歷,但是正如上面所說(shuō)的多線程也存在一些缺點(diǎn),例如:
統(tǒng)一個(gè)資源可能會(huì)被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源,當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題,那么這里就需要強(qiáng)調(diào)一下線程鎖的概念:
關(guān)于線程鎖的說(shuō)明,有一個(gè)最經(jīng)典的例子就是購(gòu)票系統(tǒng)的例子:下面我也根據(jù)這個(gè)場(chǎng)景說(shuō)明一下線程鎖的使用及重要性:
#import "TicketManager.h"
@interface TicketManager ()
/**
剩余票數(shù)
*/
@property (nonatomic,assign)NSInteger tickets;
/**
賣出票數(shù)
*/
@property (nonatomic,assign)NSInteger saleCount;
/**
杭州賣票點(diǎn)(線程模擬)
*/
@property (nonatomic,strong)NSThread * thread_HZ;
/**
上海買票點(diǎn)(線程模擬)
*/
@property (nonatomic,strong)NSThread * thread_SH;
@end
#define TotalTicket 10// 總票數(shù)
@implementation TicketManager
- (instancetype)init{
if (self = [super init]) {
self.tickets = TotalTicket;
self.thread_HZ = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
[self.thread_HZ setName:@"HZ_Thread"];
self.thread_SH = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
[self.thread_SH setName:@"SH_Thread"];
}
return self;
}
/// 訪問(wèn)同一份資源,票庫(kù)
-(void)sale {
while (true) {
if (self.tickets > 0) {
[NSThread sleepForTimeInterval:0.5];
self.tickets -- ;
self.saleCount = TotalTicket - self.tickets;
NSLog(@"站點(diǎn):%@, 當(dāng)前余票:%ld,售出:%ld",[NSThread currentThread].name,(long)self.tickets,(long)self.saleCount);
}
}
}
/// 開始賣票
-(void)startToSaleTicket{
[self.thread_HZ start];
[self.thread_SH start];
}
@end
這種是一個(gè)沒(méi)有線程鎖的情況,那先看一下打印的輸出結(jié)果:

從結(jié)果可以明顯的看出多線程訪問(wèn)統(tǒng)一資源的問(wèn)題,會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂。接下來(lái)就看一下幾種線程鎖:
1、互斥鎖@synchronized (self) 【使用簡(jiǎn)單,但是小號(hào)CPU資源較大】

2、NSCondition加鎖

3、NSLock加鎖

從三種加鎖方式的輸出結(jié)果可以看出,都能達(dá)到預(yù)期,能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題。至于具體使用哪種方式,可以根據(jù)自己的需求進(jìn)行選擇。
<a name="(八)">(八)總結(jié)</a>
本文只是對(duì)多線程進(jìn)行了一個(gè)簡(jiǎn)單的探索研究,希望能夠幫助到有需要的童鞋,文章提到的一些知識(shí)點(diǎn)并不是很深,需要進(jìn)行深入研究的朋友可以直接翻看官方API,如果文章中有不足的地方,歡迎指正!