- 多線程:多線程可以提升程序運行的效率,能夠同時處理多種不同的任務,避免處理一個任務的同時擱置其他任務,造成程序卡住的問題.
多線程的幾種創建方式
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方法檢測操作是否被取消,對取消做出響應