iOS筆記-多線程相關(guān)(pthread 、NSThread 、GCD、NSOperation)

隨機(jī)配圖
  • 進(jìn)程

    • 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
  • 線程

    • 1個(gè)進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個(gè)進(jìn)程至少要有1條線程)
    • 1個(gè)線程中任務(wù)的執(zhí)行是串行的(執(zhí)行完上一個(gè)才能執(zhí)行下一個(gè))
  • 多線程

    • 1個(gè)進(jìn)程中可以開啟多條線程,多條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)
    • 線程可以并行, 但是每個(gè)線程中的任務(wù)還是串行
  • 多線程原理

    • 多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)
  • 多線程優(yōu)缺點(diǎn)

    • 優(yōu)點(diǎn)
      • 能適當(dāng)提高程序的執(zhí)行效率
      • 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
    • 缺點(diǎn)
      • 線程越多,CPU在調(diào)度線程上的開銷就越大
      • 如果開啟大量的線程,會(huì)降低程序的性能
      • 程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

pthread

  • 類型:
    C語言中類型的結(jié)尾通常 _t/Ref,而且不需要使用 *
/*
參數(shù):
     1. 線程代號(hào)的地址
     2. 線程的屬性
     3. 調(diào)用函數(shù)的指針
        - void *(*)(void *)
        - 返回值 (函數(shù)指針)(參數(shù))
        - void * 和 OC 中的 id 是等價(jià)的
     4. 傳遞給該函數(shù)的參數(shù)
返回值:
     如果是0,表示正確
     如果是非0,表示錯(cuò)誤碼
*/
NSString *str = @"lnj";
    pthread_t thid;
    int res = pthread_create(&thid, NULL, &demo, (__bridge void *)(str));
    if (res == 0) {
        NSLog(@"OK");
    } else {
        NSLog(@"error %d", res);
    }

NSThread

  • 一個(gè)NSThread對(duì)象就代表一條線程
  • 創(chuàng)建線程的幾種方式
  • alloc/init
    // 1.創(chuàng)建線程
    NJThread *thread = [[NJThread alloc] initWithTarget:self selector:@selector(demo:) object:@"lnj"];
    // 設(shè)置線程名稱
    [thread setName:@"xmg"];
    // 設(shè)置線程的優(yōu)先級(jí)
    // 優(yōu)先級(jí)僅僅說明被CPU調(diào)用的可能性更大
    [thread setThreadPriority:1.0];
    // 2.啟動(dòng)線程
    [thread start];

  • detach/performSelector
    • 優(yōu)點(diǎn):簡(jiǎn)單快捷
    • 缺點(diǎn):無法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置
// 1.創(chuàng)建線程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"lnj"];

// 1.創(chuàng)建線程
// 注意: Swift中不能使用, 蘋果認(rèn)為這個(gè)方法不安全
    [self performSelectorInBackground:@selector(demo:) withObject:@"lnj"];

  • 多線程的安全隱患
    • 被鎖定的代碼同一時(shí)刻只能有一個(gè)線程執(zhí)行
@synchronized(鎖對(duì)象) { // 需要鎖定的代碼  }

  • 互斥鎖的優(yōu)缺點(diǎn)
    優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
    缺點(diǎn):需要消耗大量的CPU資源

  • 互斥鎖注意點(diǎn)

    • 鎖定1份代碼只用1把鎖,用多把鎖是無效的
    • 鎖定范圍越大, 性能越差

  • 原子和非原子屬性

    • atomic:線程安全,需要消耗大量的資源
    • nonatomic:非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備
  • 自旋鎖 & 互斥鎖

    • 共同點(diǎn)
      都能夠保證同一時(shí)間,只有一條線程執(zhí)行鎖定范圍的代碼
    • 不同點(diǎn)
      • 互斥鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會(huì)進(jìn)入"休眠"狀態(tài),等待其他線程執(zhí)行完畢,打開鎖之后,線程會(huì)被"喚醒"
      • 自旋鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會(huì)"一直等待"鎖定代碼執(zhí)行完成!
        自旋鎖更適合執(zhí)行非常短的代碼!

  • 線程間通信

    • 子線程做耗時(shí)操作, 主線程更新數(shù)據(jù)

[self performSelectorInBackground:@selector(download) withObject:nil];

/*
 waitUntilDone是否等待被調(diào)用方法執(zhí)行完成,有可能也會(huì)等待調(diào)用方法的執(zhí)行完成!
 YES: 等待被調(diào)用線程執(zhí)行完畢再執(zhí)行后面的代碼
 NO : 不用等待被調(diào)用線程執(zhí)行完畢就可以執(zhí)行后面的代碼
 */

[self performSelectorOnMainThread:@selector(showImage:) withObject:[UIImage imageWithData:data] waitUntilDone:YES];


---

###GCD
- GCD中有2個(gè)核心概念
  + 任務(wù):執(zhí)行什么操作
  + 隊(duì)列:用來存放任務(wù)

- 執(zhí)行任務(wù)
  + 同步方法: dispatch_sync
  + 異步方法: dispatch_async
  + 同步和異步的區(qū)別
      * 同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
      * 異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

- 隊(duì)列
  + 并發(fā)隊(duì)列
      * 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
      * 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
  + 串行隊(duì)列
      * 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))

- 注意點(diǎn)
  + 同步和異步主要影響:能不能開啟新的線程
      * 同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
      * 異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
  + 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
      * 并發(fā):允許多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
      * 串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)

- 各種任務(wù)隊(duì)列搭配
  + 同步 + 串行
  + 同步 + 并發(fā)
  + 異步 + 串行
  + 異步 + 并發(fā)
  + 異步 + 主隊(duì)列
  + 同步 + 主隊(duì)列
      

- GCD線程間通信

```objc
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // 執(zhí)行耗時(shí)的異步操作...
    dispatch_async(dispatch_get_main_queue(), ^{
      // 回到主線程,執(zhí)行UI刷新操作
      });
});
  • GCD其它用法
  • 延時(shí)執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后執(zhí)行這里的代碼...
});
  • 一次性代碼

    • 使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});


- 快速迭代

```objc
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
  // 執(zhí)行10次代碼,index順序不確定
});
  • barrier

    • 在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行
    • 不能是全局的并發(fā)隊(duì)列
    • 所有的任務(wù)都必須在一個(gè)隊(duì)列中

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);


- 隊(duì)列組

```objc
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  // 等前面的異步操作都執(zhí)行完畢后,回到主線程...
});

NSOperation

  • NSOperation的作用

    • 配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線程編程
  • NSOperation和NSOperationQueue實(shí)現(xiàn)多線程的具體步驟

    • 先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
    • 然后將NSOperation對(duì)象添加到NSOperationQueue中
    • 系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來
    • 將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
  • NSOperation的子類

  • NSOperation是個(gè)抽象類,并不具備封裝操作的能力,必須使用它的子類

使用NSOperation子類的方式有3種

1.NSInvocationOperation
2.NSBlockOperation
3.自定義子類繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法
  • 1.NSInvocationOperation
    • 1.創(chuàng)建NSInvocationOperation對(duì)象
      - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

    • 2.調(diào)用start方法開始執(zhí)行操作
      - (void)start;

    • 一旦執(zhí)行操作,就會(huì)調(diào)用target的sel方法

注意
- 默認(rèn)情況下,調(diào)用了start方法后并不會(huì)開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
- 只有將NSOperation放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作

  • 2.NSBlockOperation
    • 1.創(chuàng)建NSBlockOperation對(duì)象
      + (id)blockOperationWithBlock:(void (^)(void))block;

    • 通過addExecutionBlock:方法添加更多的操作
      - (void)addExecutionBlock:(void (^)(void))block;

注意
- 只要NSBlockOperation封裝的操作數(shù) > 1,就會(huì)異步執(zhí)行操作


NSOperationQueue

  • NSOperationQueue的作用

    • NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
    • 如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作
  • 添加操作到NSOperationQueue中
    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block;

最大并發(fā)數(shù)

  • 什么是并發(fā)數(shù)

    • 同時(shí)執(zhí)行的任務(wù)數(shù)
    • 比如,同時(shí)開3個(gè)線程執(zhí)行3個(gè)任務(wù),并發(fā)數(shù)就是3
  • 最大并發(fā)數(shù)的相關(guān)方法
    - (NSInteger)maxConcurrentOperationCount;
    - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

隊(duì)列的取消、暫停、恢復(fù)

  • 取消隊(duì)列的所有操作

    - (void)cancelAllOperations;

    提示:也可以調(diào)用NSOperation的- (void)cancel方法取消單個(gè)操作

  • 暫停和恢復(fù)隊(duì)列
    - (void)setSuspended:(BOOL)b; // YES代表暫停隊(duì)列,NO代表恢復(fù)隊(duì)列
    - (BOOL)isSuspended;

操作依賴

  • NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
    • 比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫
      [operationB addDependency:operationA]; // 操作B依賴于操作A

    • 可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系

操作的監(jiān)聽

  • 可以監(jiān)聽一個(gè)操作的執(zhí)行完畢
    - (void (^)(void))completionBlock;
    - (void)setCompletionBlock:(void (^)(void))block;

自定義NSOperation

  • 自定義NSOperation的步驟
    • 重寫- (void)main方法,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)

    • 重寫- (void)main方法的注意點(diǎn):

      • 自己創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無法訪問主線程的自動(dòng)釋放池)
      • 經(jīng)常通過- (BOOL)isCancelled方法檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)

NSOperationQueue和GCD對(duì)比

+ GCD
    * 并發(fā): 自己創(chuàng)建, 全局
    * 串行: 自己創(chuàng)建, 主隊(duì)列
+ NSOperationQueue
    * 主隊(duì)列: mainQueue
        + 永遠(yuǎn)在主線程中執(zhí)行
    * 自己創(chuàng)建隊(duì)列: alloc init
        + 會(huì)開啟新的線程, 在子線程中執(zhí)行
+ 如何控制并行和串行
    * maxConcurrentOperationCount = -1 ; 并行
    * 默認(rèn)就是并行
    * maxConcurrentOperationCount = 1 ; 串行
    * maxConcurrentOperationCount = 0 ; 不會(huì)執(zhí)行
+ 使用步驟:
    * 和GCD一樣
    * 1.創(chuàng)建操作(任務(wù))
    * 2.將任務(wù)添加到隊(duì)列中
+ 快速添加任務(wù)的方法
// 只要利用隊(duì)列調(diào)用addOperationWithBlock:方法, 系統(tǒng)內(nèi)部會(huì)自動(dòng)封裝成一個(gè)NSBlockOperation \
    然后再添加到隊(duì)列中
[queue addOperationWithBlock:^{
        NSLog(@"3 == %@", [NSThread currentThread]);
    }];
  • 隊(duì)列的暫停和恢復(fù)以及取消
    • 暫停
      • self.queue.suspended = YES;
      • 注意點(diǎn):暫停其實(shí)是暫停下一個(gè)任務(wù), 而不能暫停當(dāng)前任務(wù)
    • 恢復(fù)
      • self.queue.suspended = NO;
      • 注意點(diǎn): 恢復(fù)之后會(huì)繼續(xù)執(zhí)行隊(duì)列中沒有被執(zhí)行的操作
    • 取消
      • [self.queue cancelAllOperations];
      • 實(shí)現(xiàn)原理: 調(diào)用所有操作的cancel方法
      • 注意點(diǎn): 取消其實(shí)是取消下一個(gè)任務(wù), 而不能取消當(dāng)前任務(wù)
      • 如果自定義操作中做了很多耗時(shí)操作, 蘋果建議定期檢查是否已經(jīng)取消了

- (void)main
{
    // 耗時(shí)操作1
    for (int i = 0; i < 10000; i++) { // 500
        NSLog(@"%i ==== %@", i, [NSThread currentThread]);
    }
    NSLog(@"++++++++++++++++++++++++++++++++++++++");
    if (self.isCancelled) {
        return;
    }

    // 耗時(shí)操作2
    for (int i = 0; i < 10000; i++) { // 500
        NSLog(@"%i ==== %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) {
        return;
    }
    NSLog(@"++++++++++++++++++++++++++++++++++++++");
    // 好所操作3
    for (int i = 0; i < 10000; i++) { // 500
        NSLog(@"%i ==== %@", i, [NSThread currentThread]);
    }
}
  • 隊(duì)列之間的依賴
    • 在操作添加到隊(duì)列之前, 利用操作調(diào)用addDependency, 就快要添加依賴
    • 添加依賴之后, 只有所有依賴的任務(wù)都執(zhí)行完畢, 才會(huì)執(zhí)行當(dāng)前任務(wù)
    • 注意點(diǎn): 不要相互依賴
    • 特點(diǎn): 跨隊(duì)列依賴(GCD默認(rèn)是不支持)
 // 3.添加依賴
    [op5 addDependency:op1];
    [op5 addDependency:op2];
    [op5 addDependency:op3];
    [op5 addDependency:op4];
  • 操作的監(jiān)聽

    • 只需要利用操作調(diào)用completionBlock即可
    • 只要任務(wù)執(zhí)行完畢, 就會(huì)回調(diào)completionBlock
  • 線程間的通信

    • 將任務(wù)添加到自己創(chuàng)建的隊(duì)列中
    • 再利用mainQueue回到主隊(duì)列
最后編輯于
?著作權(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)容

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