多線程基本概念

  • 多線程:多線程可以提升程序運行的效率,能夠同時處理多種不同的任務,避免處理一個任務的同時擱置其他任務,造成程序卡住的問題.

多線程的幾種創(chuàng)建方式

1.pthread:基于c語言,需要手動管理線程的生命周期,開發(fā)中一般不用.

  • 使用方法:引入#import <pthread.h>頭文件

代碼如下:

//定義只想函數(shù)的指針函數(shù)
void *run(void *pragra)
{
    return NULL;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{   //開啟線程
    pthread_t thread;
    //創(chuàng)建線程
    pthread_create(&thread, NULL, run, NULL);
}
pthread_create參數(shù):
(<#pthread_t *restrict#>:傳入線程的地址
<#const pthread_attr_t *restrict#>:線程的屬性,一般填NULL
<#void *(*)(void *)#>:指向函數(shù)的指針函數(shù),執(zhí)行線程時會調用該函數(shù)
<#void *restrict#>)一般填NULL

2.NSThread:基于OC,更加面向對象,簡單易用,可直接操作線程對象.偶爾使用.

  • 使用方法:無需引入頭文件
- 第一種方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{   //獲取當前線程
    NSThread *current = [NSThread currentThread];
    //創(chuàng)建線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];// 線程一啟動,就會在線程thread中執(zhí)行self的run方法
    //設置線程名字
    thread.name = @"副線程";
    //開啟線程
    [thread start];
    // 獲得主線程
    [NSThread mainThread];
    // 是否為主線程
    [thread isMainThread];
    // 是否為主線程
    [NSThread isMainThread];
}
- 第二種方法
  //創(chuàng)建線程后自動啟動線程
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
- 第三種方法
 //隱式創(chuàng)建并啟動線程
    [self performSelectorInBackground:@selector(run) withObject:nil];
上述2種創(chuàng)建線程方式的優(yōu)缺點
優(yōu)點:簡單快捷
缺點:無法對線程進行更詳細的設置
  • 其他常用設置:
    //設置線程阻塞時間
    [NSThread sleepForTimeInterval:0.5];
    [NSThread sleepUntilDate:[NSDate distantFuture]];
    //設置線程優(yōu)先級
    [NSThread setThreadPriority:NSOperationQueuePriorityNormal];
    //結束線程
    [NSThread exit];

線程優(yōu)先級:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
  • 線程鎖

互斥鎖使用格式:


@synchronized(鎖對象) { // 需要鎖定的代碼  }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
互斥鎖的優(yōu)缺點
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點:需要消耗大量的CPU資源

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

相關專業(yè)術語:線程同步
線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務)
互斥鎖,就是使用了線程同步技術
  • 線程之間的通信:
  • 子線程的任務執(zhí)行完之后,一般會需要回到主線程,繼續(xù)執(zhí)行任務.
//回到主線程執(zhí)行click方法
    [self performSelectorOnMainThread:@selector(click) withObject:nil waitUntilDone:NO];
     [self performSelectorOnMainThread:@selector(click) withObject:nil waitUntilDone:NO modes:nil];

3.GCD(Grand Central Dispatch)

基本概念

  • GCD的優(yōu)勢

    GCD是蘋果公司為多核的并行運算提出的解決方案

    GCD會自動利用更多的CPU內核(比如雙核、四核)

    GCD會自動管理線程的生命周期(創(chuàng)建線程、調度任務、銷毀線程)

    程序員只需要告訴GCD想要執(zhí)行什么任務,不需要編寫任何線程管理代碼

  • GCD中有2個核心概念

    任務:執(zhí)行什么操作

    隊列:用來存放任務

  • GCD的使用需要2個步驟

    1.定制任務

    2.確定想做的事情

  • 將任務添加到隊列中

    GCD會自動將隊列中的任務取出,放到對應的線程中執(zhí)行

    任務的取出遵循隊列的FIFO原則:先進先出,后進后出

  • 區(qū)別:

    同步:只能在當前線程中執(zhí)行任務,不具備開啟新線程的能力

    異步:可以在新的線程中執(zhí)行任務,具備開啟新線程的能力

  • GCD的隊列可以分為2大類型

  • 并發(fā)隊列(Concurrent Dispatch Queue)
    可以讓多個任務并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務)
    并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

  • 串行隊列(Serial Dispatch Queue)
    讓任務一個接著一個地執(zhí)行(一個任務執(zhí)行完畢后,再執(zhí)行下一個任務)

  • 有4個術語比較容易混淆:同步、異步、并發(fā)、串行

    同步和異步主要影響:能不能開啟新的線程

    同步:在當前線程中執(zhí)行任務,不具備開啟新線程的能力

    異步:在新的線程中執(zhí)行任務,具備開啟新線程的能力

  • 并發(fā)和串行主要影響:任務的執(zhí)行方式

    并發(fā):多個任務并發(fā)(同時)執(zhí)行

    串行:一個任務執(zhí)行完畢后,再執(zhí)行下一個任務

使用方法:

GCD分為兩種方式來執(zhí)行線程:

//用同步的方式執(zhí)行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務
//用異步的方式執(zhí)行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務

代碼如下:

//創(chuàng)建queue
  dispatch_queue_t queue;
  //使用異步方式執(zhí)行線程
  dispatch_async(queue, ^{
     NSLog(@"%@",[NSThread currentThread]);
    });
//同步執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    //創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue1 = dispatch_queue_create("self.com", DISPATCH_QUEUE_CONCURRENT)

創(chuàng)建并發(fā)隊列的參數(shù)解析:

   //函數(shù)名:
   dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
   參數(shù):
   const char *label:隊列名稱
   dispatch_queue_attr_t attr:隊列類型
   
   隊列類型有兩種:
   DISPATCH_QUEUE_CONCURRENT :并發(fā)
   DISPATCH_QUEUE_SERIAL NULL :串行(默認為NULL)  

獲得全局隊列

//獲得全局隊列:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
參數(shù)解析:
long identifier:可以通過dispatch_queue_priority_t指定隊列優(yōu)先級
unsigned long flags:此參數(shù)目前暫時無用,傳0即可

dispatch_queue_priority_t的參數(shù)類型如下:
#define DISPATCH_QUEUE_PRIORITY_HIGH        2(高)
#define DISPATCH_QUEUE_PRIORITY_DEFAULT     0(默認)
#define DISPATCH_QUEUE_PRIORITY_LOW         (-2)(低)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND  INT16_MIN(后臺)

獲得串行隊列有兩種方法:

  //1.使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
 dispatch_queue_t queue = dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
 例:
 dispatch_queue_t queue = dispatch_queue_create("self.com", NULL);
 dispatch_release(queue); // 非ARC需要釋放手動創(chuàng)建的隊列

 參數(shù):
 const char *label:隊列名稱
 dispatch_queue_attr_t attr:一般用NULL即可
 
  隊列類型有兩種:
    DISPATCH_QUEUE_CONCURRENT :并發(fā)
    DISPATCH_QUEUE_SERIAL NULL :串行(默認為NULL) 
    
 //2.使用主隊列(跟主線程相關聯(lián)的隊列)
 //主隊列是GCD自帶的一種特殊的串行隊列
//放在主隊列中的任務,都會放到主線程中執(zhí)行

dispatch_queue_t queue = dispatch_get_main_queue();

線程通信

 //獲得系統(tǒng)創(chuàng)建的線程,異步執(zhí)行
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執(zhí)行耗時的異步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執(zhí)行UI刷新操作
        });
});

延時執(zhí)行

iOS常見的延時執(zhí)行有2種方式
調用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調用self的run方法

使用GCD函數(shù)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
    
});

一次性代碼:

使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行1次的代碼(這里面默認是線程安全的)
});

隊列組

首先:分別異步執(zhí)行2個耗時的操作
其次:等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作

如果想要快速高效地實現(xiàn)上述需求,可以考慮用隊列組
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í)行完畢后,回到主線程...
});

4.NSOperation

NSOperation的簡介與用法

配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程.

  • NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟:

    • 將需要執(zhí)行的操作封裝到一個NSOperation對象中

    • 將NSOperation對象添加到NSOperationQueue中

    • 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來

    • 將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行

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

  • 使用NSOperation子類的方式有3種:

    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現(xiàn)內部相應的方法
  • 使用方法如下:

創(chuàng)建線程:

    //創(chuàng)建一個NSInvocationOperation線程,如果不調用addOperation:方法,默認會在主線程執(zhí)行
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    //創(chuàng)建NSBlockOperation類型的線程,默認會開啟新的線程,如果線程數(shù)小于1,默認會同步執(zhí)行(不開啟新線程)
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
        //再上一個NSBlockOperation基礎上增加線程,會開啟新的線程執(zhí)行
        [op1 addExecutionBlock:^{
        }];
    }];
    ```
    
>開始/取消線程:
 ```objc
  //開始線程
  [op start];
  //取消線程
  [op cancel];

監(jiān)聽線程執(zhí)行完成事件:

//監(jiān)聽線程執(zhí)行完成事件
[op setCompletionBlock:^{
    //
}];
op.completionBlock = ^ {
    //
};

設置線程依賴:

    //設置線程依賴(執(zhí)行完op之后才會執(zhí)行op1)
    [op1 addDependency:op];

創(chuàng)建隊列,并添加線程:

//創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//將線程op添加到隊列,自動開啟新的線程執(zhí)行,不需要調用start方法
[queue addOperation:op];
//添加線程到隊列中,自動開啟新的線程執(zhí)行
[queue addOperationWithBlock:^{
    NSLog(@"%s",__func__);
}];

設置隊列屬性:

//設置隊列最大并發(fā)線程數(shù)
queue.maxConcurrentOperationCount = 3;
[queue setMaxConcurrentOperationCount:3];
//取消所有線程
[queue cancelAllOperations];
//暫停隊列
[queue setSuspended:YES];
queue.suspended = YES;

自定義線程

  • 自定義線程需要繼承自NSOperation.
  • 重寫- (void)main方法,在里面實現(xiàn)想執(zhí)行的任務
  • 重寫- (void)main方法的注意點:
    自己創(chuàng)建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放池)
    經(jīng)常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容