多線程:一個進程裏面開啓多條線程,每條線程可以單獨的執行不同的任務。
iOS實現多線程的方式:
1、pthread(C寫的、基本不用) 2、NSThread 3、gcd 4、NSOperation
下面分別介紹下後三個常用的多線程方式
NSThread:
使用方式
// 方式1
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 開啓
// 方式2
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 方式3
[self performSelectorInBackground:@selector(run) withObject:nil];
介紹NSThread,要首先介紹一下線程的生命週期:新建-就緒-運行-阻塞-死亡
優點:比較輕量,使用方式更加靈活,可以很直觀的控制線程對象。例如直接取消線程,也可以自定線程。
缺點:需要自己管理線程的生命週期、線程同步。
解釋一下線程同步:多條線程按順序執行任務。NSThread通過加鎖實現,加鎖對系統資源有一定的消耗。
下面的兩種方式不用關心線程管理,數據同步的問題。
GCD:
使用方式
dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); // 串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue.serial", DISPATCH_QUEUE_SERIAL); //並行隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//獲取全局的併發隊列
dispatch_queue_t queue = dispatch_get_main_queue();//獲取主隊列
dispatch_sync(queue, ^{ // 同步函數 要求馬上執行
//do something
});
dispatch_async(queue, ^{ // 異步函數 等主線程執行完,在開線程執行任務
// do something
});
GCD是蘋果爲多核並行運算提出的解決方案,會自動利用多核,自動管理線程的生命週期(創建,調度,銷燬)。
那麼GCD是如何自己管理生命週期和線程同步的問題呢,有兩個概念 隊列(queue) 和 任務 (task,上面的block),
使用方式已經在上面列出,下面總結一下兩個函數和各種隊列的使用效果:
首先是同步函數dispatch_sync,無論是並行隊列還是串行隊列,都不會開啓新線程,並同步執行。
異步函數dispatch_async在串行(非主隊列)或並行隊列中都會開啓新線程,不同的是串行隊列裏是串行執行任務,異步隊列是併發執行任務。如果是在主隊列,不開啓新線程,串行執行任務。
下面說一下死鎖的問題:
dispatch_sync有個特性是不等當前任務執行完成立即開啓下個任務,如果下個任務還是在當前隊列執行任務,就會造成相互等待(死鎖)。
舉個例子
// 當前在主隊列裏
dispatch_queue_t queue = dispatch_get_main_queue(); // 獲取主隊列
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
//同步執行任務,這時候主隊列停止,等待sync添加的任務,
而sync添加的任務是在當前隊列裏執行NSLog,
NSLog又要等當前的隊列執行完上個任務才能執行,就陷入了相互等待。。。
線程之間通信:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
// 異步執行任務
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主隊列
NSLog(@"%@",[NSThread currentThread]);
});
});
取消任務
iOS8之後可以調用dispatch_block_cancel
來取消(需要注意必須用dispatch_block_create
創建dispatch_block_t
)
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"block1 %@",[NSThread currentThread]);
});
dispatch_async(queue, block);
dispatch_block_cancel(block);
需要注意的是這種方式只能取消還沒開始的任務
第二種取消方式就是模仿NSOperation裏面的isCanceled。就是執行任務的時候加判斷
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
__block BOOL isCancel = NO;
dispatch_async(queue, ^{
sleep(3);
if(isCancel){
// 任務取消了
}else{
// 沒有取消,繼續執行
}
});
NSOperation
NSOperation是對gcd的封裝,面向對象,並多了一些簡單的功能。
NSOperation和NSOperationQueue實現多線程的具體步驟
1.將需要執行的操作封裝到一個NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
系統會自動將NSOperationQueue中的NSOperation取出來,並將取出的NSOperation封裝的操作放到一條新線程中執行
NSOperation是個抽象類,要想使用必須繼承該類,系統提供了兩個直接能用的類
NSInvocationOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
[op start];
NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
自定義類例如 MyOperation 繼承NSOperation
實現main方法
NSOperationQueue:
NSOperation可以調用start方法來執行任務,但默認是同步執行的
如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作
下面是添加到queue的方法
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
還有三個方法重點說一下
- maxConcurrentOperationCount 設置大的併發數
- suspended 暫停任務
- cancelAllOperations 取消所有任務,這裏也是隻能取消當前沒有開始的任務。(要想取消當前的任務,需要在任務裏隨時判斷isCanceled變量)
通過上面的總結,希望能回答兩個問題(1、iOS實現多線程的方式,各自的特點,優缺點 2,多線程使用要注意什麼,gcd爲什麼會造成死鎖?)。
多線程應用的好,可以提升app的運行效率,流暢度,運用不好的話也會有許多的負面效果,比如開啓線程是很消耗系統資源的,不能無限制開啓,再比如死鎖等問題。所以以後在項目裏要靈活運用多線程。
(最近就遇到一個問題,搜索列表問題,隨着用戶的輸入,實時顯示搜索到的列表(本地數據,實時過濾,當然量比較大),如果只是運用gcd的get_global_queue實現多線程的話會有問題,因爲任務回調回來的時機不同,會造成顯示的列表不對。然後我就用了Operation來解決的這個問題,當來新的搜索任務也就是用戶輸入改變的時候,如果上個任務還沒有完成,則取消之前的任務。)