多線程基本概念

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

多線程的幾種創建方式

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

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

代碼如下:

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

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

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

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

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

互斥鎖使用格式:


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

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

相關專業術語:線程同步
線程同步的意思是:多條線程在同一條線上執行(按順序地執行任務)
互斥鎖,就是使用了線程同步技術
  • 線程之間的通信:
  • 子線程的任務執行完之后,一般會需要回到主線程,繼續執行任務.
//回到主線程執行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的優勢

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

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

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

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

  • GCD中有2個核心概念

    任務:執行什么操作

    隊列:用來存放任務

  • GCD的使用需要2個步驟

    1.定制任務

    2.確定想做的事情

  • 將任務添加到隊列中

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

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

  • 區別:

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

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

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

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

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

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

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

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

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

  • 并發和串行主要影響:任務的執行方式

    并發:多個任務并發(同時)執行

    串行:一個任務執行完畢后,再執行下一個任務

使用方法:

GCD分為兩種方式來執行線程:

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

代碼如下:

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

創建并發隊列的參數解析:

   //函數名:
   dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)
   參數:
   const char *label:隊列名稱
   dispatch_queue_attr_t attr:隊列類型
   
   隊列類型有兩種:
   DISPATCH_QUEUE_CONCURRENT :并發
   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#>)
參數解析:
long identifier:可以通過dispatch_queue_priority_t指定隊列優先級
unsigned long flags:此參數目前暫時無用,傳0即可

dispatch_queue_priority_t的參數類型如下:
#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函數創建串行隊列
 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需要釋放手動創建的隊列

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

dispatch_queue_t queue = dispatch_get_main_queue();

線程通信

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

延時執行

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

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

一次性代碼:

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

隊列組

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

如果想要快速高效地實現上述需求,可以考慮用隊列組
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.NSOperation

NSOperation的簡介與用法

配合使用NSOperation和NSOperationQueue也能實現多線程編程.

  • NSOperation和NSOperationQueue實現多線程的具體步驟:

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

    • 將NSOperation對象添加到NSOperationQueue中

    • 系統會自動將NSOperationQueue中的NSOperation取出來

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

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

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

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

創建線程:

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

監聽線程執行完成事件:

//監聽線程執行完成事件
[op setCompletionBlock:^{
    //
}];
op.completionBlock = ^ {
    //
};

設置線程依賴:

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

創建隊列,并添加線程:

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

設置隊列屬性:

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

自定義線程

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

推薦閱讀更多精彩內容