多線程的實現(xiàn)方案(4種)

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:https://yotrolz.com/posts/c8aae65b/


一、pthread(很少使用)

簡介:

  • 一套通用的多線程API
  • 使用與Unix、Linux、Window等系統(tǒng)
  • 跨平臺、可移植
  • 使用難度大

特點:

  • C語言
  • 程序員管理線程生命周期

使用方法:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    pthread_t thread;

    NSLog(@"%@", [NSThread currentThread]); // 主線程中執(zhí)行

    pthread_create(&thread, NULL, run, NULL); // 在子線程中執(zhí)行演示的操作

}

/** 延時操作 */
void * run(void *param) {

    // 耗時操作
    for (NSInteger i = 0; i < 10000; i++) {
        NSLog(@"%zd", i);
    }

    NSLog(@"%@", [NSThread currentThread]); // 子線程中執(zhí)行
    return NULL;
}

二、NSTread(偶爾使用)

簡介:

  • 使用更加面向?qū)ο?/li>
  • 使用簡單,可直接操作線程對象

特點:

  • OC語言
  • 程序員管理線程生命周期

線程狀態(tài):

線程狀態(tài).png

常用方法:

  • 創(chuàng)建子線程-方式一

// 創(chuàng)建一個子線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
// 設(shè)置創(chuàng)建的線程的名字
[thread setName:@"sonThread"];
// 開啟這個線程
[thread start];
```

  • 創(chuàng)建子線程-方式二

// 方式二: 創(chuàng)建后無需手動開啟,系統(tǒng)會自動開啟
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
```

  • 創(chuàng)建子線程-方式三

// 方式三:隱式創(chuàng)建并自定開啟
[self performSelectorInBackground:@selector(test) withObject:nil];
```

  • 獲得當(dāng)前線程

    NSThread *current = [NSThread currentThread];
    
  • 線程的名字

    // 設(shè)置線程的名字
    - (void)setName:(NSString *)name;
    // 獲取線程的名字
    - (NSString *)name;
    
  • 啟動線程

    - (void)start;
    // 進(jìn)入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進(jìn)入死亡狀態(tài)
    
  • 阻塞線程

    + (void)sleepUntilDate:(NSDate *)date;
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    // 進(jìn)入阻塞狀態(tài)
    
  • 強制殺死線程

    + (void)exit;
    // 進(jìn)入死亡狀態(tài)
    
    • 注意:一旦線程死亡(停止),就不能再次開啟

線程安全

  • 線程安全隱患


    線程安全隱患.png
  • 解決方式:互斥鎖

    線程安全解決方式.png

  • 互斥鎖的使用前提:多條線程搶奪同一塊資源

  • 互斥鎖的優(yōu)缺點:

    • 優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
    • 缺點:需要消耗大量的CPU資源
  • 相關(guān)專業(yè)術(shù)語:線程同步

線程通信

  • 在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)
  • 線程通信常用方法
// 在主線程執(zhí)行SEL
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 在指定的thread執(zhí)行SEL
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;

三、GCD(經(jīng)常使用)

  • 簡介:
    • 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”
    • 旨在替代NSTread等線程技術(shù)
    • 充分利用設(shè)備的多核
  • 特點:
    • C語言
    • 系統(tǒng)自動管理線程生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
  • GCD兩個重要概念:任務(wù)隊列
    • 任務(wù):執(zhí)行什么操作(任務(wù))
    • 隊列:用來存放任務(wù)
  • GCD的使用步驟:
    • 1.定制任務(wù)
    • 2.將任務(wù)添加到隊列中
    • 注意:任務(wù)的取出(執(zhí)行),按照隊列的FIFO原則(先進(jìn)先出,后進(jìn)后出)
  • GCD有四個用來執(zhí)行任務(wù)的常用函數(shù)
    • 同步執(zhí)行:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    • 異步執(zhí)行:可以(但不一定)在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
    dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
    
    dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
    ```
- 隊列的類型:(Serial Dispatch Queue和Concurrent Dispatch Queue)
    - 1.串行隊列:讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
    - 2.并發(fā)隊列:可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
    - `注意`:并發(fā)隊列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
- 同步、異步、并發(fā)、串行
    - 同步和異步主要影響:`能不能開啟新的線程`
        - 同步:只是在`當(dāng)前線程`中執(zhí)行任務(wù),不具備開啟新線程的能力
        - 異步:`可以(但不是一定)`在`新的線程`中執(zhí)行任務(wù),具備開啟新線程的能力
    - 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
        - 并發(fā):允許多個任務(wù)并發(fā)(同時)執(zhí)行
        - 串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)

- 隊列的創(chuàng)建
    - 并發(fā)隊列的創(chuàng)建:
    ```objc
    // 創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
    ```
    - GCD默認(rèn)已經(jīng)提供了`全局的并發(fā)隊列`,供整個應(yīng)用使用,可以無需手動創(chuàng)建,我們只要獲取就可以使用
    ```objc
    dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優(yōu)先級
unsigned long flags); // 此參數(shù)暫時無用,用0即可(官方文檔)

    // 獲得全局并發(fā)隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 全局并發(fā)隊列的優(yōu)先級
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
    ```
    - 串行隊列的創(chuàng)建:
        - 1.創(chuàng)建一個串行隊列:
        ```objc
        dispatch_queue_t queue = dispatch_queue_create("com.YotrolZ", DISPATCH_QUEUE_SERIAL);
        ```
            - 注意:隊列類型傳入`NULL`也可以;

        - 2.獲取系統(tǒng)自帶的一種特殊的串行隊列-`主隊列`
        ```objc
        dispatch_queue_t queue = dispatch_get_main_queue()
        ```
            - 主隊列的特點:放在`主隊列`中的任務(wù),都是在`主線程`中執(zhí)行
- 注意點:
    - 在串行隊列中使用同步函數(shù)添加任務(wù)時會出現(xiàn)卡主當(dāng)前串行隊列的現(xiàn)象!!
    - 解釋:在串行隊列中,任務(wù)是一個一個按順序執(zhí)行了,當(dāng)使用同步函數(shù)往隊列中添加任務(wù)時理論上要立即執(zhí)行改任務(wù),但由于串行隊列還沒有執(zhí)行完畢,理論上又輪不到剛添加的任務(wù)執(zhí)行,會出現(xiàn)你等我,我等你的現(xiàn)象

- GCD的其他使用:
    - 1.延時執(zhí)行
    ```objc
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 這里協(xié)商2秒后要執(zhí)行的代碼
});
    ```
    - 2.一次性代碼(讓某段代碼在整個程序運行過程中`只運行一次`)
        - 一次性代碼函數(shù)是`線程安全`的

    ```objc
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 這里寫上只執(zhí)行1次的代碼
    });
    ```
    - 3.快速迭代
        - 注意點:index`順序不確定`

    ```objc
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
        // 執(zhí)行10次代碼
    });
    ```
    - 4.隊列組
    ```objc
    dispatch_group_t group =  dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 執(zhí)行1個耗時的異步操作
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 執(zhí)行1個耗時的異步操作
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步操作都執(zhí)行完畢后,回到主線程執(zhí)行此操作
    });
    ```

### 四、NSOperation+NSOperationQueue(經(jīng)常使用)
- 簡介:
    - NSOperation基于GCD(底層是GCD),性能肯定也就沒有GCD高了
    - 比GCD多了一些更簡單的功能
    - 使用更面向?qū)ο?- 特點:
    - OC語言
    - 系統(tǒng)`自動管理`線程生命周期

#### NSOperation的使用
- NSOperation是一個`抽象類`,并不具備封裝任務(wù)的功能,我們應(yīng)該使用其子類(3種)
- `NSBlockOperation`
    - NSBlockOperation使用方法:
    ```objc
    NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation1--%@", [NSThread currentThread]); // 在主線程執(zhí)行
    }];

    [blockOP addExecutionBlock:^{
        NSLog(@"blockOperation2--%@", [NSThread currentThread]); // 在子線程執(zhí)行(自動開啟線程)
    }];

    [blockOP addExecutionBlock:^{
        NSLog(@"blockOperation3--%@", [NSThread currentThread]); // 又會開啟一個新的子線程
    }];

    [blockOP start];
    ```
    - NSBlockOperation使用細(xì)節(jié):
        - 1.單獨使用blockOperation且`任務(wù)只有一個`時,不會開啟新的線程,在`當(dāng)前線程`中執(zhí)行;
        - 2.為blockOperation`再次添加新的任務(wù)`(任務(wù)數(shù)大于1)會開啟新的線程,在`子線程`中執(zhí)行;

- `NSBlockOperation`
    - NSInvocationOperation使用方法:
    ```objc
    -(void)invocationOperation {
    // 創(chuàng)建任務(wù)
    NSInvocationOperation *invocationOP = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

    // 開始任務(wù)
    [invocationOP start];
    }
    -(void)run {
    NSLog(@"invocationOperation--%@", [NSThread currentThread]);

    }
    ```
    - NSInvocationOperation使用細(xì)節(jié):
    單獨使用operation不會開啟新的線程,在`當(dāng)前線程`中執(zhí)行

- `自定義Operation`
    - 步驟1.定義一個子類繼承`NSOperation`;
    - 步驟2.實現(xiàn)其`- (void)main;`方法,在main方法中添加要執(zhí)行的任務(wù)操作;
        ```objc
        - (void)main { // 添加要執(zhí)行的任務(wù)操作
        if (self.isCancelled) return;
        for (NSUInteger i = 0; i < 1000; i++) {
            NSLog(@"%zd--%@", i, [NSThread currentThread]);
            }

        if (self.isCancelled) return;
        for (NSUInteger i = 0; i < 1000; i++) {
            NSLog(@"%zd--%@", i, [NSThread currentThread]);
            }

        if (self.isCancelled) return;
        for (NSUInteger i = 0; i < 1000; i++) {
            NSLog(@"%zd--%@", i, [NSThread currentThread]);
            }

        }
        ```
    - 自定義Operation使用細(xì)節(jié):
        - 重寫Operation的mian方法時,官方建議我們及時的判斷`isCancelled`,以能夠及時的取消一些耗時的操作;

- NSOperation的其他使用
    - 操作的`依賴`和`監(jiān)聽`

    ```objc
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 創(chuàng)建5個任務(wù)操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op2--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op3--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op4--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op5--%@", [NSThread currentThread]);
    }];

    // 操作的監(jiān)聽
    op4.completionBlock = ^{
        // 也是在子線程中執(zhí)行,但是不一定和器任務(wù)操作在一個線程
        NSLog(@"op4完成了--%@", [NSThread currentThread]);
    };

    // 設(shè)置依賴(可以設(shè)置多個,但是不能相互依賴)
    // 這樣就保證了op3在op1和op4完成后才執(zhí)行
    [op3 addDependency:op1];
    [op3 addDependency:op4];

    // 將任務(wù)操作添加到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
    }
    ```
#### NSOperationQueue的使用
- NSOperationQueue的作用
    - NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的,不一定會開啟新的線程
    - 將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會`自動異步執(zhí)行`NSOperation中的操作

- 將任務(wù)(NSOperation)添加到操作隊列(NSOperationQueue0中
    - 方式一:需先創(chuàng)建NSOperation,再添加到NSOperationQUeue中
    ```objc
    -(void)addOperation:(NSOperation *)op;
    ```
    - 方式二:不需先創(chuàng)建NSOperation,直接在添加到NSOperationQueue的時候在block中寫上任務(wù)代碼

    ```objc
    -(void)addOperationWithBlock:(void (^)(void))block;
    ```
    ```objc
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 任務(wù)1
    [queue addOperationWithBlock:^{
        NSLog(@"11--%@", [NSThread currentThread]);

        for (NSUInteger i = 0; i < 10; i++) {
            NSLog(@"%zd", i);
            [NSThread sleepForTimeInterval:1];
        }
    }];
    // 任務(wù)2
    [queue addOperationWithBlock:^{
        NSLog(@"22--%@", [NSThread currentThread]);
        for (NSUInteger i = 0; i < 10; i++) {
            NSLog(@"%zd", i);
        }
    }];

    // 任務(wù)3
    [queue addOperationWithBlock:^{
        NSLog(@"33--%@", [NSThread currentThread]);
        for (NSUInteger i = 0; i < 10; i++) {
            NSLog(@"%zd", i);
        }
    }];

    ```
    - 備注:添加到NSOperationQueue中的任務(wù)系統(tǒng)會`自動異步執(zhí)行`,不用start方法

- NSOperationQueue的常見屬性:
    - 1.最大并發(fā)數(shù):同時執(zhí)行的任務(wù)數(shù)
    ```objc
    @property NSInteger maxConcurrentOperationCount;
    ```
    - 備注:設(shè)置`NSOperationQueue`的`maxConcurrentOperationCount`為`1`時,也就是同時執(zhí)行一個任務(wù),(`同步執(zhí)行`)

    - 2.暫停(掛起狀態(tài))
    ```objc
    @property (getter=isSuspended) BOOL suspended;
    ```
    - 備注:設(shè)置`NSOperationQueue`的`suspended`為`YES`時,會暫停(掛起)隊列中的任務(wù),但是如果某個任務(wù)已經(jīng)在執(zhí)行了,并不會立即暫停這個任務(wù),而是等待這個任務(wù)執(zhí)行完畢后,暫停后面的其他任務(wù);

    - 使用場合:暫停(掛起)這個功能,可以用在(假如正在下載一組圖片,用戶拖拽了界面控件等,為了保證流暢的用戶體驗,可以先掛起,等用戶拖拽結(jié)束,再恢復(fù))的情況下使用

- NSOperationQueue取消所有任務(wù):
    ```objc
    -(void)cancelAllOperations;
    ```
    - 備注:NSOperationQueue對象調(diào)用`cancelAllOperations`方法時,會`取消隊列中的所有任務(wù)`,但是如果某個任務(wù)已經(jīng)在執(zhí)行了,并不會立即取消所有任務(wù),而是等待這個任務(wù)執(zhí)行完畢后,取消所有任務(wù);
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容