1. 程式人生 > >Objective-C基礎筆記整理(三)多執行緒篇

Objective-C基礎筆記整理(三)多執行緒篇

多執行緒

1、基礎概念

  • 程序:在系統中正在執行的一個應用程式,例如開啟常用的一個軟體,系統會啟動一個程序,每個執行緒之間是相互獨立的。
  • 執行緒:一個程序要想執行任務,必須至少有一條執行緒(主執行緒),執行緒是程序執行任務的最小單位。
  • 執行緒的序列:一個執行緒只能執行一個任務,如果要執行多個任務,那麼只能一個一個地按順序執行這些任務。
  • 多執行緒:一個程序可以開啟多條執行緒,每條執行緒可以同時執行不同的任務。

多執行緒的原理

1、CPU只能處理一條執行緒,只能在一條執行緒中工作。
2、多執行緒併發,其實是CPU快速的在多條執行緒之間的排程。
3、如果開啟的執行緒過多會消耗大量的CPU資源,降低執行效率。
4、一般開啟 3 ~ 5 個執行緒。

2、多執行緒實現方案

  • pthread:通用的多執行緒API,適用於Unix、Linux、Windows等系統,跨平臺、可移植,使用難度大,C語言,執行緒生命週期由程式設計師管理。
  • NSTread:面向物件,簡單易用,可直接操作執行緒物件,OC語言,執行緒生命週期由程式設計師管理。
  • GCD:開發中常用的一種方案,替代NSTread等執行緒技術,充分利用裝置的多核,C語言,執行緒生命週期自動管理。
  • NSOperation:底層是GCD,比GCD多了一些更簡單實用的功能,使用更加面向物件,OC語言,執行緒生命週期自動管理。

2.1、pthread

建立執行緒物件

    pthread_t thread;

建立執行緒

    /*
     第一個引數 執行緒物件
     第二個引數 執行緒屬性
     第三個引數 要執行的任務
     第四個引數 函式的引數
     */
    pthread_create(&thread, NULL, run ,NULL);

設定子執行緒的狀態設定為detach,該執行緒執行結束後會自動釋放所有資源。

    pthread_detach()

執行方法

    void *run(void *param)
{
    for (NSInteger i = 0; i < 10000
; i++) { NSLog(@"%zd", i); } return NULL; }

2.2、NSTread

建立執行緒

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

設定執行緒名字

thread.name = @"running";

qualityOfService 設定優先順序

    /*
     NSQualityOfServiceUserInteractive: 最高:主要用於與UI互動的操作,各種事件處理以及繪製圖像等。
     NSQualityOfServiceUserInitiated: 次高:執行一些明確需要立即返回結果的任務。
     NSQualityOfServiceDefault: 預設:預設的優先順序,介於次高階和普通級之間。
     NSQualityOfServiceUtility: 普通:用於執行不許要立即返回結果、耗時的操作,下載或者一些媒體操作等。
     NSQualityOfServiceBackground: 後臺:後臺執行一些使用者不需要知道的操作,它將以最有效的方式執行。例如一些預處理的操作,備份或者同步資料等等。
     */
    thread.qualityOfService = NSQualityOfServiceUserInitiated;

開啟執行緒

[thread start];

其它常用方法

 //判斷是否是多執行緒
 + (BOOL)isMultiThreaded;
 //執行緒休眠
 + (void)sleepUntilDate:(NSDate *)date;
 //執行緒休眠時間
 + (void)sleepForTimeInterval:(NSTimeInterval)ti;
 //退出當前執行緒
 + (void)exit;
 //獲取當前執行緒優先順序
 + (double)threadPriority;
 // 設定執行緒優先順序 預設為0.5 取值範圍為0.0 - 1.0,1.0優先順序最高
 + (BOOL)setThreadPriority:(double)p;

執行緒通訊

1、在1個程序中,多個執行緒之間需要經常進行通訊。
2、例如我們在子執行緒獲取資料後,要回到主執行緒重新整理介面。
3、1個執行緒傳遞資料給另1個執行緒。
4、在1個執行緒中執行完特定任務後,轉到另1個執行緒繼續執行任務。

回到主執行緒或者轉到另一個執行緒的方法

// 返回主執行緒
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定執行緒
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

執行緒狀態描述

1、建立執行緒物件後,執行緒處於新建New的狀態。
2、呼叫start 開啟執行緒後,會將執行緒加入到可排程執行緒池中,此時進入就緒Runnable狀態。
3、當CPU排程到當前執行緒的時候,這時就會處於執行Running的狀態。
4、當CPU排程其他執行緒的時候,當前執行緒又回到就緒狀態。
5、當我們對當前執行緒呼叫了sleep或者等待同步鎖的時候,此時又會進入執行緒阻塞Blocked狀態。
   加入到可排程執行緒池中。
6、當任務執行完畢或強制退出後,進入到死亡Dead狀態,此時執行緒物件被釋放。

借一張圖表示一下:

這裡寫圖片描述

執行緒安全

1、問題:多條執行緒搶奪同一塊資源,可能會導致資料錯亂和資料安全的問題。
2、解決:互斥鎖,加鎖可以使執行緒同步(多條執行緒在同一條線上按順序的執行任務)。
3、優點:有效防止因多執行緒搶奪資源造成的資料安全問題。
4、缺點:需要消耗大量的CPU資源。
    //self : 鎖物件,必須全域性唯一
    @synchronized (self) {
        //程式碼片段,需要做的操作
    }

2.3、GCD


GCD優點

  • 自動管理執行緒生命週期(建立執行緒–>排程任務–>銷燬執行緒)。
  • 只需要告訴GCD想要執行什麼任務,不需要編寫任何執行緒管理程式碼。

核心概念:任務和佇列

  • 任務:執行什麼操作。
  • 佇列:存放任務。

    1、將任務新增到佇列中,GCD會自動將佇列中的任務取出,放到對應的執行緒中執行。
    2、任務的取出遵循佇列的FIFO原則:先進先出,後進後出。
    

任務

(1)、同步任務:只能在當前執行緒執行任務,不具備開啟新執行緒的能力.

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

(2)、非同步任務:在新的執行緒中執行任務,具備開啟執行緒的能力.

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

佇列

(1)、併發佇列:可以讓多個任務同時執行,併發功能只有在非同步函式下才有效.

    //1.建立併發佇列
    /**
     第一個引數:c語言字串,標籤
     第二個引數:佇列的型別
     DISPATCH_QUEUE_CONCURRENT  併發
     DISPATCH_QUEUE_SERIAL      序列
     */
    dispatch_queue_t creatCurrrentQueue = dispatch_queue_create("creatCurrrentQueue", DISPATCH_QUEUE_CONCURRENT);


    //2.獲取全域性的併發佇列
    /**
     第一個引數:優先順序  選擇預設的優先順序
     第二個引數:留給以後用的  暫時傳0
     */
    dispatch_queue_t getGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

(2)、序列佇列:讓任務一個接一個執行.

    //1.建立序列佇列
    dispatch_queue_t creatQueue = dispatch_queue_create("creatQueue", DISPATCH_QUEUE_SERIAL);

    //2.獲取全域性的序列佇列主佇列 主佇列是GCD自帶的一種特殊的序列佇列,放在主佇列中的任務,都會放到主執行緒中執行
    dispatch_queue_t getQueue = dispatch_get_main_queue();

常用組合方式

    // 非同步函式 + 獲取全域性併發佇列 --- 例如獲取資料
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 非同步函式 + 獲取全域性序列佇列(主佇列)--- 例如 回到主執行緒 重新整理UI介面
        dispatch_async(dispatch_get_main_queue(), ^{

        });

    });

同步函式 + 主佇列:死鎖

     //1.獲得主佇列
    dispatch_queue_t queue =  dispatch_get_main_queue();
    //2.同步函式
    dispatch_sync(queue, ^{
        NSLog(@"---download1---%@",[NSThread currentThread]);
    });
1、給主執行緒中新增任務,同步函式要求立刻馬上執行任務,而主佇列安排主執行緒來執行任務,
   但當前主執行緒在等待方法執行完畢, 因此就會相互等待而發生死鎖。
2、如果此方法在子執行緒中呼叫,則不會形成死鎖。

其它GCD使用方式

(1)、延遲執行

    /*
     第一個引數:延遲時間
     第二個引數:要執行的程式碼
     如果想讓延遲的程式碼在子執行緒中執行,也可以更改在哪個佇列中執行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
     */
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
        //執行內容
    });

(2)、只執行一次,例如單例

        //整個程式執行過程中只會執行一次
        //onceToken用來記錄該部分的程式碼是否被執行過
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"-----");
        });

(3)、佇列組

    dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 執行1個耗時的非同步操作

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 執行1個耗時的非同步操作

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的非同步操作都執行完畢後,回到主執行緒...

});

(4)、快速迭代:開啟多條執行緒,併發執行,相比於for迴圈在耗時操作中極大的提高效率和速度

        /*
     第一個引數:迭代的次數
     第二個引數:在哪個佇列中執行
     第三個引數:block要執行的任務
     */
    dispatch_apply(10, queue, ^(size_t index) {
    });

2.4、NSOperation

NSOperation是蘋果對GCD的封裝,完全面向物件,不用考慮執行緒的生命週期、同步、加鎖等問題。

NSOperation和NSOperationQueue分別對應GCD的任務和佇列。

NSOperation和NSOperationQueue實現多執行緒的具體步驟:

1、將需要執行的操作封裝到一個NSOperation物件中。
2、將NSOperation物件新增到NSOperationQueue中。
3、系統會自動將NSOperationQueue中的NSOperation取出來,將其封裝的操作放到一條新執行緒中執行。

NSOperation

NSOperation是個抽象類,並不具備封裝操作的能力,必須使用它的子類,它的子類有三種:

(1)、NSInvocationOperation

    /*
     第一個引數:目標物件
     第二個引數:選擇器,要呼叫的方法
     第三個引數:方法要傳遞的引數
     */
    NSInvocationOperation *op  = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    //啟動操作
    [op start];

(2)、NSBlockOperation(常用)

    //1.封裝操作
    NSBlockOperation *blockOperation= [NSBlockOperation blockOperationWithBlock:^{
        //要執行的操作,在主執行緒中執行
    }];

    //2.追加操作,追加的操作在子執行緒中執行,可以追加多條操作
    [blockOperation addExecutionBlock:^{
        //追加的操作
    }];
    //啟動操作
    [blockOperation start];

(3)、自定義子類繼承NSOperation,實現內部相應的方法

    -(void)main
    {
    // 要執行的操作
    }

NSOperationQueue

佇列:
(1)、主佇列:通過mainQueue獲得,凡是放到主佇列中的任務都將在主執行緒執行
(2)、非主佇列:直接alloc init出來的佇列。非主佇列同時具備了併發和序列的功能,通過設定最大併發數屬性來控制任務是併發執行還是序列執行
(3)、NSOperation可以呼叫start方法來執行任務,但預設是同步執行的,如果將NSOperation新增到NSOperationQueue(操作佇列)中,系統會自動非同步執行NSOperation中的操作

新增操作到NSOperationQueue中

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

NSOperation和NSOperationQueue結合使用建立多執行緒

    // 1. 建立非主佇列 同時具備併發和序列的功能,預設是併發佇列
    NSOperationQueue *queue =[[NSOperationQueue alloc]init];
    // 2. 封裝操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        //操作
    }];
    // 3. 將封裝操作加入主佇列
    // 也可以不獲取封裝操作物件 直接新增操作到佇列中
    //[queue addOperationWithBlock:^{
    // 操作
    //}];
    [queue addOperation:op1];

NSOperation和NSOperationQueue的重要屬性和方法

NSOperation的依賴

// 操作one依賴two,即one必須等two執行完畢之後才會執行
[one addDependency:two];

// 移除依賴
[one removeDependency:two];

NSOperation操作監聽

op1.completionBlock = ^{
        NSLog(@"op1已經完成了---%@",[NSThread currentThread]);
    };

NSOperation執行緒之間的通訊方法

// 回到主執行緒重新整理UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
      //重新整理操作
}];