多線程
- 進程
- 進程是指在系統中正在運行的一個應用程序
- 線程
- 一個進程想要執行任務,必須得有線程
- 一個進程的所有任務都在線程中執行
- 線程的串行
- 一個線程中任務的執行時串行的,就是一個個的按順序執行
- 多線程
- 一個進程中可以開啟多條線程,每條線程可以并行(同時)執行不同的任務
- 原理:
- 同一時間,CPU只能處理一條線程,只有一條線程在工作
- 多線程并發執行,其實是CPU快速地在多條線程之間調度
- 如果CPU調度線程的速度夠快,就造成了多線程并發執行的家鄉
- 如果線程過多,CPU在這么多線程之間調度,消耗大量的CPU資源
- 優點:
- 能適當提高程序的執行效率
- 能適當提高資源利用率(CPU,內存利用率)
- 缺點:
- 創建線程是有開銷的,ios下主要成本包括:
- 內核數據結構(大約1kb)
- 棧空間(子線程512KB,主線程1MB,也可以使用-setStactSize:設置,但必須是4K的倍數,而且最小是16K)
- 創建線程大約需要90毫秒的創建時間
- 如果開啟大量的線程,會降低程序的性能
- 線程越多,CPU在調度線程上的開銷就越大
- 程序更加復雜:比如線程之間的通信,多線程的數據共享
- 創建線程是有開銷的,ios下主要成本包括:
- IOS中多線程的實現方案
- pthread
- NSTread
- GCD
- NSOperation
NSTread
常用創建NSTread的方法:
方法1:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject: (nullable id)argument;
/*
特點:是快速創建一個線程并啟動它,讓它執行run:方法,Object可以傳入任何對象作為 run方法的參數.此方法無返回值,也就無法從這個方法拿到創建好的線程
*/
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"];
方法2:
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
/*
特點:初始化并返回一個NSThread,再給這個NSTread添加一個run方法,object:可以 傳run方法的參數,也可以傳nil,初始化完畢需要手動啟動線程。
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];
方法3:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
/*
特點:隱式的創建一個線程并啟動它這個方法所在聲明文件是@interface NSObject (NSThreadPerformAdditions),說 明只要繼承了NSObject類就可以使用,此方法無返回值。
*/
[self performSelectorInBackground:@selector(run:) object:@"test"]
獲取NSTread的方法:
獲取當前NSTread的方法:
+ (NSThread *)currentThread;NSThread *thread = [NSThread currentThread]; //獲取當前所在線程
獲取主線程的方法:
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);NSThread *thread = [NSThread mainThread];
控制線程狀態的一些常用方法:
啟動當前線程對象方法
- (void)start NS_AVAILABLE(10_5, 2_0);
//創建一個線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];
//啟動這個線程
[thread start];
取消當前線程對象方法
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//創建一個線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];
//啟動這個線程
[thread start];
//取消這個線程
[thread cancel];
注:如果是線程在執行一個循環操作,cancel取消不了這個操作.只能等循環結束
阻塞線程方法1:
NSDate *date = [NSDate date];
//這個方法返回一個表示當前時間的NSDate對象
date = [date dateByAddingTimeInterval:60];
//這個方法會在原本時間基礎上加60秒
[NSThread sleepUntilDate:date1];
//這個方法的作用是讓當前線程一直阻塞到時間為date1的時候,上面的例子的意思就是讓 當前線程阻塞到60秒后
阻塞線程方法2:
[NSThread sleepForTimeInterval:60];
//這個方法的作用是讓當前線程阻塞固定的秒數
退出當前線程的方法:
[NSThread exit];
//注:一旦此線程退出,就無法再啟動
查看線程狀態的一些常用方法:
查看當前線程優先級
double priority = [NSThread threadPriority];
//返回值是double,從0.0-1.0,1.0優先級最高
判斷當前所在線程是否為主線程
BOOL result = [NSThread isMainThread];
設置線程的一些方法
設置當前線程的優先級
BOOL result = [NSThread setThreadPriority:0.5];
//設置當前線程的優先級,設置成功會返回YES
檢測應用程序是否是多線程的方法
BOOL result = [NSThread isMultiThreaded];
/*
檢測應用程序是否多線程,返回值為YES時則程序為多線程 注:1.程序最開始只有一條主線程時調用此方法時,不管以后會有多少條線程,都會返回NO2.在所有非主線程都結束后,調用此方法還是會返回YES
*/
NSTread的常用屬性
線程優先級屬性
property double threadPriority NS_AVAILABLE(10_6, 4_0);
property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
/*
NSQualityOfService的枚舉值優先級從高到低如下:
NSQualityOfServiceUserInteractive = 0x21 主要用于與UI交互的操作,各種事件處理以及繪制圖像等.
NSQualityOfServiceUserInitiated = 0x19 執行一些明確需要立即返回結果的任務.例如,用戶在郵件列表中選擇郵件后加載電子郵件
NSQualityOfServiceDefault = -1 默認
NSQualityOfServiceUtility = 0x11 用于執行不需要立即返回結果,耗時的操作,下載或者一些媒體操作等.
NSQualityOfServiceBackground = 0x09后臺執行一些用戶不需要知道的操作,它將以最有效的方式運行.例如一些與處理的操作,備份或者同步數據等等.*/
當前線程對象是否為主線程
property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
當前線程對象是否正在執行任務
property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
當前線程對象是否已執行完任務
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
當前線程對象是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
線程的名稱
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
線程間通信的方法:
方法1:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/*
特點:
在子線程里調用這個方法意味著,將會回到主線程里去調用self的aSelector方法,arg處是給aSelector方法傳的參數
注:wait參數是表示是否阻塞這個子線程,如果為YES,則要子線程要等待主線程執行完aSelector方法才會繼續往下執行。
聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
*/
方法2:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
//只比上個方法多了一個modes參數,是用來設置runLoop模式
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
方法3:
- (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> )array NS_AVAILABLE(10_5, 2_0);
//只比上個方法多了一個onThread參數,意思就是可以從任意的兩個線程之間作轉換
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
方法4:
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject: (nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
//類似上面的方法,只少了設置runLoop模式的參數
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
線程安全 –互斥鎖
- synhronized(鎖對象){//需要鎖定的代碼}
- 鎖對象只能是一個對象,不能是多個對象
- 多條線程同時操作同一處數據的時候,就要加鎖
- 缺點:互斥鎖需要消耗大量的CPU資源
- 優點:防止多個對象同時操作同一數據,造成安全問題
- 線程同步:多條線程在同一條線上按順序執行,互斥鎖就用到線程同步
GCD
簡介
- 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器”
- 純C語言,提供了非常多強大的函數
- 優勢:
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內核
- GCD會自動管理線程的生命周期(創建線程,調度任務,銷毀線程)
- 程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼
- 任務:執行什么操作
- 隊列:用來存放任務
- 使用步驟
- 定制任務
- 將任務添加到隊列中
- GCD會自動將隊列中的任務取出,放到對應的線程中執行
- 任務的取出遵循隊列的FIFO原則:先進先出,后進后出
兩個用來執行任務的函數
- 同步
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
/*
queue:隊列
block:任務
*/
- 異步
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步和異步的區別
同步:只能在當前線程中執行任務,不具備開啟新線程的能力
異步:可以在新的線程中執行任務,具備開啟新線程的能力
隊列的類型
- 并發隊列
- 可以讓多個任務并發執行(自動開啟多個線程同時執行任務)
- 并發功能只有在異步函數下才有效
- GCD默認已經提供了全局的并發隊列,供整個應用使用
dispatch_get_global_queue(dispatch_queue_priority_t priority, // 隊列的優先級
unsigned long flags);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲得全局并發隊列全局并發隊列的優先級
#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 // 后臺
- 串行隊列
- 讓任務一個接著一個地執行
- GCD中獲得串行有2種途徑
方法1:
使用dispatch_queue_create函數創建串行隊列
dispatch_queue_create(const char *label, // 隊列名稱
dispatch_queue_attr_t attr); // 隊列屬性,一般用NULL即可
dispatch_queue_t queue = dispatch_queue_create("myThread", NULL); // 創建
dispatch_release(queue); // 非ARC需要釋放手動創建的隊列
方法2:
使用主隊列(跟主線程相關聯的隊列)
* 主隊列是GCD自帶的一種特殊的串行隊列
* 放在主隊列中的任務,都會放到主線程中執行
* 使用dispatch_get_main_queue()獲得主隊列
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刷新操作
});
});
- 延時執行
方法1:調用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調用self的run方法方法2:使用GCD函數dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執行這里的代碼...
});
- 一次性代碼
使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行一次
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(), ^{
// 等前面的異步操作都執行完畢后,回到主線程...
});
NSOperation
NSOperation和NSOperationQueue實現多線程的步驟
- 先將要執行的操作封裝到一個NSOperation對象中
- 然后將NSOperation對象添加到NSOperationQueue中
- 系統會自動將NSOperationQueue中的NSOperation取出來
- 將取出的NSOperation封裝的操作放到一條新線程中執行
NSOperation的子類
- NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
- 使用NSOperation子類的方式有三種
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實現內部相應的方法
NSInvocationOperation
//創建NSInvocationOperation對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
//調用start方法開始執行操作,一旦執行操作,就會調用target的sel方法
- (void)start;
注:默認情況下,調用了start方法后并不會開一條新線程去執行操作,注意是在當前線程同步執行操作,只有將NSOperation放到NSOperationQueue中才會異步執行操作,也就是說,在子線程調用這個方法不會跳到主線程執行任務,只會在這個子線程執行
NSBlockOperation
//創建NSBlockOperation對象
+ (id)blockOperationWithBlock:(void (^)(void))block;
//通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作
NSOperationQueue
- NSOperationQueue的作用
- NSOperation可以調用start方法來執行任務,但默認是同步執行的
- 如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作
- 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
- 最大并發數相關的方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- 隊列的取消,暫停,恢復
//取消隊列的所有操作
- (void)cancelAllOperations;
提示:也可以調用NSOperation的- (void)cancel方法取消單個操作
//暫停和恢復隊列
- (void)setSuspended:(BOOL)b;
// YES代表暫停隊列,NO代表恢復隊列
- (BOOL)isSuspended;
- 依賴操作
- NSOperation之間可以設置依賴來保證執行順序
[operationB addDependency:operationA];
//操作B依賴于操作A
//可以在不同的queue的NSOperation之間創建依賴關系
- 監聽操作
可以監聽一個操作的執行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
- 自定義NSOperation的步驟
- 重寫- (void)main方法,在里面實現想執行的任務
- 重寫- (void)main方法的注意點
- 自己創建自動釋放池(因為如果異步操作,無法訪問主線程的自動釋放池)
- 通常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應