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

目錄:
<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

串行.png

并行:(簡(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

并行.png

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

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

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

在單核操作系統(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í)行方式就是:宏觀上的并行,微觀上的串行
多核操作系統(tǒng)執(zhí)行多線程.png

對(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>

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

下面就通過(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

pthread運(yùn)行結(jié)果.png

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

其實(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)主線程");
}
NSThread方式一.png

方式一是通過(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方式二.png

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];
        }
    }
}
NSThread方式三.png

在三組控制臺(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é)果:


GCD打印輸出.png

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

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

GCD測(cè)試1.png

由打印信息可以看出,三個(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)
GCD_global優(yōu)先級(jí).png

這樣可以根據(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ì)列.png

自定義并行隊(duì)列.png

由上面的這列張圖所示的輸出信息可以清楚的看出自定義串行隊(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é)果為:

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

由打印結(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:
特殊情況隊(duì)列組.png
輸出結(jié)果為:
特殊情況隊(duì)列組輸出.png
從隊(duì)列組輸出的信息可以看出,這完全不是預(yù)期的輸出效果,預(yù)期效果因?yàn)槭牵寒?dāng)任務(wù)1和任務(wù)2都執(zhí)行完之后在回調(diào)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監(jiān)聽.png
打印輸出結(jié)果:
dispatch_group_enter監(jiān)聽結(jié)果.png
由此可以看出,強(qiáng)大的GCD應(yīng)對(duì)這種情況已經(jīng)為我們提供了解決方法,使用dispatch_group_enterdispatch_group_leave便可對(duì)隊(duì)列組中的不同異步請(qǐng)求進(jìn)行監(jiān)聽,最終執(zhí)行回調(diào)dispatch_group_async方法。但是有兩點(diǎn)需要注意的是:(1)dispatch_group_enterdispatch_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)用方法


單例輸出.png

從輸出也可以看出來(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>
延遲執(zhí)行.png

這是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>

NSInvocationOperation.png

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

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

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

概念.png

NSOperationQueue.png
用輸出效果可以看出:
在使用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è)什么效果呢?

自定義NSOperation子類.png

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

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

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

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

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

<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方法:

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ù),否則就會(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);
    });
}

輸出的打印順序如下:

依賴異常.png
這明顯不是按照依賴順序輸出的!那問(wèn)題到底出在哪呢?
其實(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)行解決:
NSRunLoop進(jìn)行解決.png

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

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

結(jié)果.png

輸出的結(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é)果:


售票.png

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


互斥鎖.png

2、NSCondition加鎖
NSCondition加鎖.png

3、NSLock加鎖


NSLock加鎖.png

從三種加鎖方式的輸出結(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,如果文章中有不足的地方,歡迎指正!

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

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

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

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