淺談iOS中多線程開發(fā)

目錄:
<a href="#(一)">(一)線程與進程之間的區(qū)別</a>
<a href="#(二)">(二)為什么需要學(xué)習(xí)多線程</a>
<a href="#(三)">(三)多線程任務(wù)執(zhí)行方式</a>
<a href="#(四)">(四)多線程執(zhí)行的原理</a>
<a href="#(五)">(五)多線程的優(yōu)缺點</a>
<a href="#(六)">(六)在iOS開發(fā)中的多線程實現(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>

【文章篇幅有點偏多,有興趣的可以繼續(xù)讀下去】
一般說到線程,那么首先要區(qū)分一下線程進程,首先來簡單的區(qū)分一下兩者的關(guān)系

進程:是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位。
線程:是指進程內(nèi)的一個執(zhí)行單元,也是進程內(nèi)的可調(diào)度實體。是進程的一個實體,是CPU調(diào)度和分派的基本單位,他是比進程更小的能獨立運行的基本單位,線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(寄存器,棧,程序計數(shù)器),但是它可與同一個進程的其他線程共享進程所擁有的全部資源

<a name="(一)">(一)線程與進程之間的區(qū)別</a>

(1)地址空間:進程內(nèi)的一個執(zhí)行單元,進程至少包含一個線程,他們共享進程的地址空間,而進程有自己獨立的地址空間
(2)資源擁有:進程是資源分配和擁有的單位,同一個進程內(nèi)的線程共享進程資源 【進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響,而線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進程?!?/code>
(3)線程是處理器調(diào)度的基本單位,但進程不是
(4)二者皆可并發(fā)執(zhí)行

<a name="(二)">(二)為什么需要學(xué)習(xí)多線程</a>

因為在程序運行中,對于網(wǎng)絡(luò)請求、圖片加載、文件處理、數(shù)據(jù)存儲、任務(wù)執(zhí)行等等這些操作都需要放到異步線程中進行處理,這也顯得多線程的重要性

<a name="(三)">(三)多線程任務(wù)執(zhí)行方式</a>

主要分為兩種:串行并行

串行:(簡易)指的是多個任務(wù)按照一定順序執(zhí)行(任務(wù)執(zhí)行有順序依賴關(guān)系),例如有三個任務(wù)執(zhí)行,并且需要的執(zhí)行順序是 線程1->線程2->線程3,那么這三個任務(wù)執(zhí)行完畢所需的時間就是 t1 + t2 + t3

串行.png

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

并行.png

有一點需要明白的是:兩種任務(wù)執(zhí)行方式并沒有好壞之分的,只是根據(jù)自己的需求進行選擇使用并行執(zhí)行還是串行執(zhí)行

<a name="(四)">(四)多線程執(zhí)行的原理</a>

單核操作系統(tǒng)執(zhí)行多線程.png

在單核操作系統(tǒng)的多線程執(zhí)行,其實是采用時間片輪轉(zhuǎn)調(diào)度來實現(xiàn)的,操作系統(tǒng)會采用時間片輪轉(zhuǎn)調(diào)度的方式為每一個線程間接性的分配時間執(zhí)行任務(wù),當(dāng)線程1執(zhí)行的時候,線程2就處于阻塞或者空閑的狀態(tài),當(dāng)時間片執(zhí)行到線程2時,執(zhí)行循序有會反過來,所以對于單核操作系統(tǒng)來說的多線程執(zhí)行方式就是:宏觀上的并行,微觀上的串行
多核操作系統(tǒng)執(zhí)行多線程.png

對于多核操作系統(tǒng)來說,就可以說是真正意義上的并行執(zhí)行,因為每一個處理器都會按照時間片輪轉(zhuǎn)的方式執(zhí)行任務(wù),多個核心處理器就可以實現(xiàn)多個任務(wù)同時執(zhí)行的效果

<a name="(五)">(五)多線程的優(yōu)缺點</a>

優(yōu)點:
(1)簡化了變成模型:可以將原本放在一個線程中執(zhí)行的一些耗時或較為大的任務(wù)進行分割到多個線程中執(zhí)行
(2)更加輕量級
(3)提高了執(zhí)行效率
(4)提高資源利用率
缺點:
(1)增加了程序設(shè)計的復(fù)雜性:因為在多線程中我們需要處理的最大問題就是資源共享問題數(shù)據(jù)讀寫問題,如果兩個線程同時修改同一個數(shù)據(jù)或?qū)傩裕蜁霈F(xiàn)問題,所以在一定程度上增加了程序設(shè)計的復(fù)雜性
(2)占用內(nèi)存空間:因為如果不分場合隨意使用多線程的時候,會導(dǎo)致程序內(nèi)存的增加,這對客戶端開發(fā)來說是一個絕對不能忽視的問題,所以我們需要適度、合理的使用多線程開發(fā)
(3)增加CPU調(diào)度開銷:因為在多線程執(zhí)行任務(wù)時,是使用時間片調(diào)度的方式進行的,頻繁的切換時間片,必然會增大CPU的調(diào)度開銷

<a name="(六)">(六)在iOS開發(fā)中的多線程實現(xiàn)技術(shù)方案</a>

iOS多線程實現(xiàn)技術(shù)方法.png

下面就通過Demo對這四種方式進行一一解釋

<a name="(A)">(A)PThread</a>

#pragma mark ---- 測試 pThread
/**
 測試 pThread
 */
- (IBAction)runPThread:(id)sender {
    
    NSLog(@"我是在主線程中執(zhí)行\(zhòng)n\n");
    pthread_t pthread;
    
    pthread_create(&pthread, NULL, run, NULL);
}
/**
 C語言函數(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í)行其實也是比較簡單的,不過實現(xiàn)過程是通過C語言進行的,從創(chuàng)建方法pthread_create(<#pthread_t _Nullable *restrict _Nonnull#>, <#const pthread_attr_t *restrict _Nullable#>, <#void * _Nullable (* _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)可以看出,第一個參數(shù)是需要一個pthread 對象指針,第三個是需要一個C語言函數(shù)方法(就當(dāng)于OC中綁定的執(zhí)行方法),至于第二個和第四個參數(shù),暫時沒有什么用(其實偶也不曉得什么作用)可以直接傳入NULL

pthread運行結(jié)果.png

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

其實第一組數(shù)字24592表示的是當(dāng)前程序所處的 進程 ID,而第二組數(shù)字1923132則表示當(dāng)前所處的線程 ID,所以我們就可以通過線程ID進行判斷是否成功開啟了一個子線程

<a name="(B)">(B)NSThread</a>

NSThread可能是我們在OC開發(fā)中接觸最早的多線程實現(xiàn)技術(shù),而且NSThread的實現(xiàn)多線程的方式也有三種,下面就通過代碼做解釋

NSThread的實現(xiàn)方式一:

#pragma mark ---- 測試 NSThread
/**
 測試 NSThread
 */
- (IBAction)runNSThread:(id)sender {
    NSLog(@"我是在主線程中執(zhí)行\(zhòng)n");
    /*
     創(chuàng)建方式 1 :通過 alloc initWithTarget 進行創(chuàng)建
     好處:可以通過 NSThread 對象設(shè)置一些線程屬性;例如線程 名字
     */
    NSThread * thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(runThread1) object:nil];
    [thread1 setName:@"Name_Thread1"];// 設(shè)置線程名字
    [thread1 setThreadPriority:0.1];// 設(shè)置線程優(yōu)先級
    [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)先級
    [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)主線程");
}
NSThread方式一.png

方式一是通過 alloc initWithTarget 進行創(chuàng)建,這種方式的好處是可以通過 NSThread 對象設(shè)置一些線程屬性;例如線程 名字,從控制臺信息可以看出來,當(dāng)設(shè)置了不同的NSThread對象的優(yōu)先級屬性,可以控制其執(zhí)行的順序,優(yōu)先級越高,越先執(zhí)行;而設(shè)置名字屬性后,可以通過調(diào)試監(jiān)控當(dāng)前所處線程,便于問題分析

NSThread的實現(xiàn)方式二:

    // 創(chuàng)建方式 2 :通過 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方式二.png

NSThread的實現(xiàn)方式三:

    // 創(chuàng)建方式 3 :通過 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];
        }
    }
}
NSThread方式三.png

在三組控制臺輸出結(jié)果對比可以發(fā)現(xiàn),三種方式都能達到預(yù)期效果

<a name="(C)">(C)GCD</a>

關(guān)于GCD可能也是我們開發(fā)過程中使用最多的一種方式,但是大多數(shù)可能都只是只知其一,不知其二,會用其中一兩個方法,就覺得會用GCD啦,其實這是遠(yuǎn)遠(yuǎn)不夠的,那我們就一起來探討一下GCD的強大之處:

1、GCD的描述:
純C語言開發(fā),是蘋果公司為多核的并行運算提出的解決方案,會自動利用更多的CPU內(nèi)核(比如雙核、四核),可以自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)。
2、GCD的兩個核心
2.1 任務(wù)
執(zhí)行的操作,在GCD中,任務(wù)是通過 block來封裝的。并且任務(wù)的block沒有參數(shù)也沒有返回值。
2.2 隊列存放任務(wù)包括
串行隊列
并發(fā)隊列
主隊列
全局隊列

首先還是像上面一樣通過簡單Demo看看它的基本功能:

#pragma mark ---- 測試 GCD
- (IBAction)runGCD:(id)sender {
    NSLog(@"執(zhí)行 GCD");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@" start tast 1");
        // 執(zhí)行耗時任務(wù)
        [NSThread sleepForTimeInterval:3];
        dispatch_async(dispatch_get_main_queue(), ^{
           
            NSLog(@"回調(diào)主線程刷新UI");
        });
    });
}

打印結(jié)果:


GCD打印輸出.png

同樣能夠?qū)崿F(xiàn)這樣的功能,接下來就一步步的來具體分析GCD:

<a name="(1)">(1) dispatch_get_global_queue 探究:</a>

GCD測試1.png

由打印信息可以看出,三個線程是同一時間開始執(zhí)行,同一時間結(jié)束執(zhí)行的,
這就說明GCD中的dispatch_get_global_queue是全局并發(fā)的隊列

/*
第一個參數(shù)設(shè)置隊列 優(yōu)先級,這樣可以控制任務(wù)開始執(zhí)行的先后順序,第二個參數(shù)沒有用到
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級
*/
dispatch_get_global_queue(long identifier, unsigned long flags)
GCD_global優(yōu)先級.png

這樣可以根據(jù)自己的需要控制任務(wù)開始執(zhí)行的先后順序。但是如果想讓任務(wù)結(jié)束的時間也按照我們的意愿進行,那就需要使用到串行隊列,我們可以根據(jù)需要自定義串行隊列或者并行隊列

/*
自定義隊列 queue
參數(shù)一:隊列標(biāo)識符
參數(shù)二:定義隊列是串行還是并行,NULL(默認(rèn))或者 DISPATCH_QUEUE_SERIAL 為串行,DISPATCH_QUEUE_CONCURRENT 表示并行隊列
*/
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>)
自定義串行隊列.png

自定義并行隊列.png

由上面的這列張圖所示的輸出信息可以清楚的看出自定義串行隊列和并行隊列的區(qū)別。

<a name="(2)">(2)dispatch_group的探索:</a>

隊列組就是可以對多個隊列進行操作的一個組,在隊列組中可以對不同隊列進行操作監(jiān)聽結(jié)果等等,首先來說一下隊列組的監(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");
       /*
           并非另外開辟一個新線程,而是在三個任務(wù)中的其中一個子線程進行回調(diào),
           所以如果需要進行刷新 UI的話,需要回調(diào)到主線程處理
        */
       dispatch_async(dispatch_get_main_queue(), ^{
           NSLog(@"回調(diào)主線程刷新UI");
       });
   });

運行結(jié)果為:

組隊咧監(jiān)聽通知.png

由打印結(jié)果可以看出,將三個并行隊列放入到隊列組中時,使用dispatch_group_notify方法可以對隊列執(zhí)行的結(jié)果進行監(jiān)聽,而且這個監(jiān)聽回調(diào)只有在隊列組中的三個異步線程都處理完成時才會執(zhí)行回調(diào),這在我們實際開發(fā)過程中也是一項非常常見的需求!
不少童鞋看到這里可能覺得會用dispatch_group_notify隊列組了,但是還有一種更常見的情況是需要倍加注意的,具體請見下列demo:
特殊情況隊列組.png
輸出結(jié)果為:
特殊情況隊列組輸出.png
從隊列組輸出的信息可以看出,這完全不是預(yù)期的輸出效果,預(yù)期效果因為是:當(dāng)任務(wù)1和任務(wù)2都執(zhí)行完之后在回調(diào)dispatch_group_notify,現(xiàn)在打印的結(jié)果卻是:任務(wù)1和任務(wù)2開始之后,隊列組就回調(diào)了dispatch_group_notify,頓時感覺自己使用了一個假的dispatch_group隊列組......
其實這才是實際開發(fā)中最常遇到的場景:當(dāng)我們執(zhí)行的任務(wù)中調(diào)起了一個異步的API請求,那么只要這個異步請求開始發(fā)送之后,dispatch_group_async就會認(rèn)為當(dāng)前任務(wù)已經(jīng)處理完畢,之后這個異步API處理的事情就不在我的監(jiān)控范圍之內(nèi)啦,所以就造成了這種打印結(jié)果的出現(xiàn)。
那么面對這種情況,需要如何處理才能正確監(jiān)聽任務(wù)執(zhí)行結(jié)果呢?如下處理:
使用dispatch_group_enter監(jiān)聽.png
打印輸出結(jié)果:
dispatch_group_enter監(jiān)聽結(jié)果.png
由此可以看出,強大的GCD應(yīng)對這種情況已經(jīng)為我們提供了解決方法,使用dispatch_group_enterdispatch_group_leave便可對隊列組中的不同異步請求進行監(jiān)聽,最終執(zhí)行回調(diào)dispatch_group_async方法。但是有兩點需要注意的是:(1)dispatch_group_enterdispatch_group_leave的使用必須是成對出現(xiàn);(2)dispatch_group_leave必須放在任務(wù)的最后一句執(zhí)行
當(dāng)然GCD的隊列組的奧秘遠(yuǎn)不止這些,目前只是列出了常用的集中以及使用場景,如果感興趣的大神可以繼續(xù)參考官方API研究!

<a name="(3)">(3)dispatch_once探究:</a>

dispatch_once是GCD提供的一種創(chuàng)建單例的API方法,因為在我們的實際開發(fā)過程中,單例也是非常常用的一個場景,例如全局的數(shù)據(jù)、公共對象等等這些都需要通過單例進行處理,而單例顧名思義,就是在工程的整個運行過程中只會創(chuàng)建一次,然后會存在于內(nèi)存中。例如:

/// 單例的創(chuàng)建
+(instancetype)instance {
    static dispatch_once_t onceToken;
    static SingleTest * inst = nil;
    dispatch_once(&onceToken, ^{
        NSLog(@"初始化單例對象");
        inst = [[SingleTest alloc]init];
    });
    return inst;
}

調(diào)用方法


單例輸出.png

從輸出也可以看出來,只有當(dāng)?shù)谝淮吸c擊方法時會創(chuàng)建對象,之后點擊方法時將不會在次創(chuàng)建對象,所有打印的對象內(nèi)存地址都相同,證明是同一個單例對象

<a name="(4)">(4)dispatch_after探究:</a>
延遲執(zhí)行.png

這是GCD中提供的一個延時操作API,使用起來很簡單,但是在個方法會存在一個陷阱,當(dāng)延時操作開始之后將無法取消,所以當(dāng)在一個界面執(zhí)行延時操作時,界面消失之后仍然會執(zhí)行操作,這樣就可能造成程序crash,所以使用的時候需要多加注意。以上就是對GCD進行的一個簡單了解

<a name="(D)">(D)NSOperation</a>

1、NSOperation簡介
1.1 NSOperation與GCD的區(qū)別:
OC語言中基于 GCD 的面向?qū)ο蟮姆庋b;
使用起來比 GCD 更加簡單;
提供了一些用 GCD 不好實現(xiàn)的功能;
蘋果推薦使用,使用 NSOperation 程序員不用關(guān)心線程的生命周期
1.2 NSOperation的特點
NSOperation 是一個抽象類,抽象類不能直接使用,必須使用它的子類
抽象類的用處是定義子類共有的屬性和方法

2、核心概念
將操作添加到隊列,異步執(zhí)行。相對于GCD創(chuàng)建任務(wù),將任務(wù)添加到隊列。
將NSOperation添加到NSOperationQueue就可以實現(xiàn)多線程編程

3、操作步驟
先將需要執(zhí)行的操作封裝到一個NSOperation對象中
然后將NSOperation對象添加到NSOperationQueue中
系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
<a name="(D1)">(1)NSInvocationOperation探究</a>

NSInvocationOperation.png

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

<a name="(D2)">(2)NSBlockOperation探究</a>

NSBlockOperation.png
可以發(fā)現(xiàn)NSBlockOperation打印的結(jié)果和上面NSInvocationOperation如出一轍,一毛一樣,這也就證明了系統(tǒng)提供的兩個子類NSInvocationOperation ``NSBlockOperation都是同步執(zhí)行的

<a name="(D3)">(3)NSOperationQueue探究</a>

首先來看一下其相關(guān)的概念及關(guān)鍵詞

概念.png

NSOperationQueue.png
用輸出效果可以看出:
在使用NSOperationQueue對象addOperation的方式執(zhí)行任務(wù),而不是通過 start執(zhí)行,輸出打印的結(jié)果會有明顯的不同
1、NSOperationQueue執(zhí)行任務(wù)會開啟一個新線程
2、NSOperationQueue執(zhí)行任務(wù)是一個異步的操作過程

<a name="(D4)">(4)自定義NSOperation子類探究 </a>

首先我們可以創(chuàng)建一個NSOperation的子類,并且重寫main方法,在代碼中是一個什么效果呢?

自定義NSOperation子類.png

調(diào)用與輸出結(jié)果.png
從結(jié)果看出執(zhí)行任務(wù)依然是開啟了一個新線程,而且也是異步執(zhí)行的過程。

<a name="(D4.1)">(4.1)maxConcurrentOperationCount 屬性: </a>

未設(shè)置并發(fā)數(shù)時,默認(rèn)所有任務(wù)同時并發(fā)執(zhí)行

未設(shè)置并發(fā)數(shù).png

當(dāng)設(shè)置了最大并發(fā)數(shù)為 2 時,如下圖可以看出NSOperationQueue同時執(zhí)行的任務(wù)數(shù)也為兩個,當(dāng)前兩個任務(wù)執(zhí)行完畢之后才繼續(xù)執(zhí)行后面的任務(wù)
最大并發(fā)數(shù).png

<a name="(D4.2)">(4.2)addDependency 方法添加依賴: </a>

一般在我們的實際開發(fā)過程中,會遇到異步任務(wù)一需要等待異步任務(wù)二完成之后才能執(zhí)行,這種情況下可以就會想到使用多線程的依賴進行實現(xiàn)(當(dāng)然使用上面說的GCD也可以),那下面就說一下 NSOperation中的addDependency方法:

addDependency.png
首先要看一下Demo中的依賴關(guān)系是如何添加的?

   [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ù),否則就會造成死鎖】;使用看到了最終控制臺輸出的順序效果。

當(dāng)然上面這是一種理想的狀態(tài),如果出現(xiàn)了下面這種 “ 變態(tài) ” 情況,這種依賴關(guān)系還可靠嗎???
當(dāng)自定NSOperation的自定義類中的main方法執(zhí)行的是一個異步任務(wù):

-(void)main{  
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1];
        if (self.cancelled) {
            return ;
        }
        NSLog(@"---%@",self.operName);
    });
}

輸出的打印順序如下:

依賴異常.png
這明顯不是按照依賴順序輸出的!那問題到底出在哪呢?
其實是因為自定義的NSOperation子類main方法中,因為main方法執(zhí)行的是一個異步任務(wù),當(dāng)任務(wù)開始執(zhí)行之后,NSOperation子類就默認(rèn)依賴任務(wù)完成,而無法監(jiān)聽到這個異步任務(wù)執(zhí)行結(jié)束。
但是這種場景也是實際開發(fā)中經(jīng)常用到的,所以要怎樣處理呢?解決方法就是使用NSRunLoop進行解決:
NSRunLoop進行解決.png

while (!self.over && !self.cancelled) {
        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

在代碼中的這句作用就是讓當(dāng)前的 RunLoop 在main方法中等待異步任務(wù)的結(jié)束,這樣一來問題就完美解決啦,下面看一下輸出效果:

結(jié)果.png

輸出的結(jié)果符合自己設(shè)置的依賴預(yù)期,問題完美解決。

<a name="(七)">(七)線程鎖相關(guān)</a>

多線程在開發(fā)中給我們帶來了很多遍歷,但是正如上面所說的多線程也存在一些缺點,例如:
統(tǒng)一個資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,那么這里就需要強調(diào)一下線程鎖的概念:
關(guān)于線程鎖的說明,有一個最經(jīng)典的例子就是購票系統(tǒng)的例子:下面我也根據(jù)這個場景說明一下線程鎖的使用及重要性:

#import "TicketManager.h"

@interface TicketManager ()
/**
 剩余票數(shù)
 */
@property (nonatomic,assign)NSInteger tickets;
/**
 賣出票數(shù)
 */
@property (nonatomic,assign)NSInteger saleCount;
/**
 杭州賣票點(線程模擬)
 */
@property (nonatomic,strong)NSThread * thread_HZ;
/**
 上海買票點(線程模擬)
 */
@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;
}
/// 訪問同一份資源,票庫
-(void)sale {
    
    while (true) {
        if (self.tickets > 0) {
            [NSThread sleepForTimeInterval:0.5];
            self.tickets -- ;
            self.saleCount = TotalTicket - self.tickets;
            
            NSLog(@"站點:%@, 當(dāng)前余票:%ld,售出:%ld",[NSThread currentThread].name,(long)self.tickets,(long)self.saleCount);
        }
    }
}
/// 開始賣票
-(void)startToSaleTicket{
    [self.thread_HZ start];
    [self.thread_SH start];
}

@end

這種是一個沒有線程鎖的情況,那先看一下打印的輸出結(jié)果:


售票.png

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


互斥鎖.png

2、NSCondition加鎖
NSCondition加鎖.png

3、NSLock加鎖


NSLock加鎖.png

從三種加鎖方式的輸出結(jié)果可以看出,都能達到預(yù)期,能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題。至于具體使用哪種方式,可以根據(jù)自己的需求進行選擇。

<a name="(八)">(八)總結(jié)</a>

本文只是對多線程進行了一個簡單的探索研究,希望能夠幫助到有需要的童鞋,文章提到的一些知識點并不是很深,需要進行深入研究的朋友可以直接翻看官方API,如果文章中有不足的地方,歡迎指正!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 主隊列 細(xì)心的同學(xué)就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當(dāng)然啦,說的是iOS中,像 pthread 這種多系統(tǒng)...
    京北磊哥閱讀 419評論 0 1
  • 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識以及使用方法。這大概是史上最詳細(xì)、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨行者閱讀 581評論 0 1
  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關(guān)多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,152評論 6 18
  • 學(xué)習(xí)多線程,轉(zhuǎn)載兩篇大神的帖子,留著以后回顧!第一篇:關(guān)于iOS多線程,你看我就夠了 第二篇:GCD使用經(jīng)驗與技巧...
    John_LS閱讀 741評論 0 3
  • 文章目錄GCD簡介任務(wù)和隊列GCD的使用步驟隊列的創(chuàng)建方法任務(wù)的創(chuàng)建方法GCD的基本使用并行隊列 + 同步執(zhí)行并行...
    lusen_b閱讀 298評論 0 1

友情鏈接更多精彩內(nèi)容