淺談iOS中多線程開發

目錄:
<a href="#(一)">(一)線程與進程之間的區別</a>
<a href="#(二)">(二)為什么需要學習多線程</a>
<a href="#(三)">(三)多線程任務執行方式</a>
<a href="#(四)">(四)多線程執行的原理</a>
<a href="#(五)">(五)多線程的優缺點</a>
<a href="#(六)">(六)在iOS開發中的多線程實現技術方案</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="#(七)">(七)線程鎖相關</a></br>
<a href="#(八)">(八)總結</a></br></br>

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

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

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

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

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

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

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

主要分為兩種:串行并行

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

串行.png

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

并行.png

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

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

單核操作系統執行多線程.png

在單核操作系統的多線程執行,其實是采用時間片輪轉調度來實現的,操作系統會采用時間片輪轉調度的方式為每一個線程間接性的分配時間執行任務,當線程1執行的時候,線程2就處于阻塞或者空閑的狀態,當時間片執行到線程2時,執行循序有會反過來,所以對于單核操作系統來說的多線程執行方式就是:宏觀上的并行,微觀上的串行
多核操作系統執行多線程.png

對于多核操作系統來說,就可以說是真正意義上的并行執行,因為每一個處理器都會按照時間片輪轉的方式執行任務,多個核心處理器就可以實現多個任務同時執行的效果

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

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

<a name="(六)">(六)在iOS開發中的多線程實現技術方案</a>

iOS多線程實現技術方法.png

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

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

#pragma mark ---- 測試 pThread
/**
 測試 pThread
 */
- (IBAction)runPThread:(id)sender {
    
    NSLog(@"我是在主線程中執行\n\n");
    pthread_t pthread;
    
    pthread_create(&pthread, NULL, run, NULL);
}
/**
 C語言函數
*/
void * run(void * data){
    
    NSLog(@"我是在子線程中執行\n\n");

    for (int i = 1; i <= 10; i++) {
        
        NSLog(@"%d \n\n",i);
        sleep(1);
    }
    
    return NULL;
}

從代碼中可以看出pThread的創建執行其實也是比較簡單的,不過實現過程是通過C語言進行的,從創建方法pthread_create(<#pthread_t _Nullable *restrict _Nonnull#>, <#const pthread_attr_t *restrict _Nullable#>, <#void * _Nullable (* _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)可以看出,第一個參數是需要一個pthread 對象指針,第三個是需要一個C語言函數方法(就當于OC中綁定的執行方法),至于第二個和第四個參數,暫時沒有什么用(其實偶也不曉得什么作用)可以直接傳入NULL

pthread運行結果.png

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

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

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

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

NSThread的實現方式一:

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

方式一是通過 alloc initWithTarget 進行創建,這種方式的好處是可以通過 NSThread 對象設置一些線程屬性;例如線程 名字,從控制臺信息可以看出來,當設置了不同的NSThread對象的優先級屬性,可以控制其執行的順序,優先級越高,越先執行;而設置名字屬性后,可以通過調試監控當前所處線程,便于問題分析

NSThread的實現方式二:

    // 創建方式 2 :通過 detachNewThreadSelector 方式創建并執行線程
    [NSThread detachNewThreadSelector:@selector(runThread2) toTarget:self withObject:nil];

/// 方式二綁定方法

-(void)runThread2{
    NSLog(@"我是在子線程中執行\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的實現方式三:

    // 創建方式 3 :通過 performSelectorInBackground 方式創建并執行線程
    [self performSelectorInBackground:@selector(runThread3) withObject:nil];

/// 方式三綁定方法

/// 方式三
-(void)runThread3{
    NSLog(@"我是在子線程中執行\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

在三組控制臺輸出結果對比可以發現,三種方式都能達到預期效果

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

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

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

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

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

打印結果:


GCD打印輸出.png

同樣能夠實現這樣的功能,接下來就一步步的來具體分析GCD:

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

GCD測試1.png

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

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

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

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

自定義并行隊列.png

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

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

隊列組就是可以對多個隊列進行操作的一個組,在隊列組中可以對不同隊列進行操作監聽結果等等,首先來說一下隊列組的監聽方法dispatch_group_notify的用法:

NSLog(@"執行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 組的監聽通知,所有task結束之后回調
   dispatch_group_notify(group, queue, ^{
       NSLog(@"All tasks over");
       /*
           并非另外開辟一個新線程,而是在三個任務中的其中一個子線程進行回調,
           所以如果需要進行刷新 UI的話,需要回調到主線程處理
        */
       dispatch_async(dispatch_get_main_queue(), ^{
           NSLog(@"回調主線程刷新UI");
       });
   });

運行結果為:

組隊咧監聽通知.png

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

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

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

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

調用方法


單例輸出.png

從輸出也可以看出來,只有當第一次點擊方法時會創建對象,之后點擊方法時將不會在次創建對象,所有打印的對象內存地址都相同,證明是同一個單例對象

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

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

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

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

2、核心概念
將操作添加到隊列,異步執行。相對于GCD創建任務,將任務添加到隊列。
將NSOperation添加到NSOperationQueue就可以實現多線程編程

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

NSInvocationOperation.png

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

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

NSBlockOperation.png
可以發現NSBlockOperation打印的結果和上面NSInvocationOperation如出一轍,一毛一樣,這也就證明了系統提供的兩個子類NSInvocationOperation ``NSBlockOperation都是同步執行的

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

首先來看一下其相關的概念及關鍵詞

概念.png

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

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

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

自定義NSOperation子類.png

調用與輸出結果.png
從結果看出執行任務依然是開啟了一個新線程,而且也是異步執行的過程。

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

未設置并發數時,默認所有任務同時并發執行

未設置并發數.png

當設置了最大并發數為 2 時,如下圖可以看出NSOperationQueue同時執行的任務數也為兩個,當前兩個任務執行完畢之后才繼續執行后面的任務
最大并發數.png

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

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

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

   [customA addDependency:customC];
   [customC addDependency:customB];
   [customB addDependency:customD];

這三句表示的依賴關系是:customA -> customC -> customB -> customD``customA任務需要在customC任務執行之后才能執行;customC任務需要在customB任務執行之后才能執行;customB任務需要在customD任務執行之后才能執行【注意:customD任務不能再依賴于customA任務,否則就會造成死鎖】;使用看到了最終控制臺輸出的順序效果。

當然上面這是一種理想的狀態,如果出現了下面這種 “ 變態 ” 情況,這種依賴關系還可靠嗎???
當自定NSOperation的自定義類中的main方法執行的是一個異步任務:

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

輸出的打印順序如下:

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

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

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

結果.png

輸出的結果符合自己設置的依賴預期,問題完美解決。

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

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

#import "TicketManager.h"

@interface TicketManager ()
/**
 剩余票數
 */
@property (nonatomic,assign)NSInteger tickets;
/**
 賣出票數
 */
@property (nonatomic,assign)NSInteger saleCount;
/**
 杭州賣票點(線程模擬)
 */
@property (nonatomic,strong)NSThread * thread_HZ;
/**
 上海買票點(線程模擬)
 */
@property (nonatomic,strong)NSThread * thread_SH;
@end

#define TotalTicket 10// 總票數

@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(@"站點:%@, 當前余票:%ld,售出:%ld",[NSThread currentThread].name,(long)self.tickets,(long)self.saleCount);
        }
    }
}
/// 開始賣票
-(void)startToSaleTicket{
    [self.thread_HZ start];
    [self.thread_SH start];
}

@end

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


售票.png

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


互斥鎖.png

2、NSCondition加鎖
NSCondition加鎖.png

3、NSLock加鎖


NSLock加鎖.png

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

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