iOS多線程方案總結及使用詳解
本篇文章整理了幾種iOS中主要的多線程方案,提供了Swift和Objective-C兩種語言的寫法。
概述
iOS目前有四種多線程解決方案:
- NSThread
- GCD
- NSOperation
- Pthread
Pthread這種方案太底層啦,實際開發中很少用到,下文主要介紹前三種方案
NSThread
NSThread是基於線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。
創建方法
Objective-C:
線程的創建與啟動
// 初始化線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFunction:) object:nil]; // 啟動線程 [thread start];
或者創建後直接啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
Swift:
//創建 let thread = NSThread(target: self, selector: "threadFunction:", object: nil) //啟動 thread.start()
或者
NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
OC中還有一種使用NSObject的方法創建線程並直接啟動的方法
[self performSelectorInBackground:@selector(run:) withObject:nil];
Swift中由於安全問題,蘋果去掉了這個方法。
其他一些常用的方法
//獲取當前線程信息 + (NSThread *)currentThread; //獲取主線程信息 + (NSThread *)mainThread; //取消線程 - (void)cancel; //獲取線程狀態 @property (readonly, getter=isExecuting) BOOL executing; @property (readonly, getter=isFinished) BOOL finished; @property (readonly, getter=isCancelled) BOOL cancelled; //設置和獲取線程名字 -(void)setName:(NSString *)n; -(NSString *)name; //使當前線程暫停一段時間,或者暫停到某個時刻 + (void)sleepForTimeInterval:(NSTimeInterval)time; + (void)sleepUntilDate:(NSDate *)date;
Swift的方法名和調用方式和OC基本一致,這裏就不一一列舉了。
GCD
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其他對稱多處理系統。
GCD是一個替代諸如NSThread等技術的很高效和強大的技術。GCD完全可以處理諸如數據鎖定和資源泄漏等復雜的異步編程問題。GCD的工作原理是讓一個程序,根據可用的處理資源,安排他們在任何可用的處理器核心上平行排隊執行特定的任務。這個任務可以是一個功能或者一個程序段。
簡單的說,GCD會自動的管理線程生命周期(創建、調度、銷毀),我們只需要告訴它該做什麽,其他的事它都會幫我們搞定,是不是很方便?
要了解GCD的使用方法,首先要明白四個概念:任務、隊列、同步執行、異步執行
任務:在GCD中任務可以被理解為一段Block代碼,即你想要執行的操作。
同步執行(同步派發):任務在同一線程內按順序一個一個執行,前一個任務執行完畢後下一個任務才會開始。換言之,當前線程會被阻塞。
異步執行(異步派發):任務在不同的線程中執行,下一個任務不用等待前一個任務執行完畢後才開始。換言之,當前線程不會被阻塞。
隊列:用來放置任務的資源池,並按照一定的規則取出並執行任務。隊列分為串行隊列和並行隊列。
- 串行隊列:遵循FIFO(先進先出)原則一個一個取出任務並執行,前一個任務完成後再取出下一個任務並執行。無論是同步還是異步執行,串行隊列都是一個一個執行任務,也就是說同一個串行隊列中的任務都是在同一個線程中執行的。
- 並行隊列:同樣遵循FIFO原則一個一個取出任務,與串行隊列的區別在於取出一個任務就會新開一個線程並在新線程中執行。同一個並行隊列中的任務一般都在不同的線程中執行
隊列的執行方式不同,產生的效果不同
同步執行 | 異步執行 | |
串行隊列 | 阻塞當前線程,直到串行隊列中的任務都執行完畢。系統不會另起線程,會在當前線程按順序一個一個地執行任務。 | 不會阻塞當前線程,系統會另起一個新線程,在新線程中按順序一個一個地執行任務。 |
並行隊列 | 阻塞當前線程,直到並行隊列中的同步派發任務都執行完畢。系統不會另起線程,會在當前線程按順序執行同步派發的任務。 | 不會阻塞當前線程,系統會另起新線程並執行,每多一個異步派發的任務系統就會多開一個線程。 |
死鎖的形成
當以同步派發的方式往當前線程所在的串行隊列添加任務時,就會形成死鎖。因為同步派發會阻塞當前線程,直到派發的任務執行完成線程才會繼續執行。而派發的任務又恰好是加入的當前被阻塞的線程,這樣會導致任務無法被執行(因為任務所在線程已被阻塞),於是形成死鎖。
創建隊列
1、主隊列
這是系統定義的串行隊列,任何會刷新UI的任務都要在主隊列中進行。因此,盡量不要在主隊列中添加很耗時的任務,確保刷新界面的任務不會被阻塞,給用戶以流暢的交互體驗。
//Objective-C dispatch_queue_t queue = ispatch_get_main_queue(); //SWIFT let queue = ispatch_get_main_queue()
2、自定義隊列
可以創建串行或並行隊列,第二個參數傳DISPATCH_QUEUE_SERIAL或NULL為串行隊列,傳DISPATCH_QUEUE_CONCURRENT為並行隊列。
//OBJECTIVE-C //串行隊列 dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", NULL); dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL); //並行隊列 dispatch_queue_t queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT); //SWIFT //串行隊列 let queue = dispatch_queue_create("serialTestQueue", nil); let queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL) //並行隊列 let queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT)
3、全局並行隊列
這是系統定義的並行隊列,並行任務一般都加入這個隊列中執行。
//OBJECTIVE-C dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //SWIFT let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
創建任務
1、同步派發(sync)
同步派發會阻塞當前線程。不管是往串行還是並行隊列中同步派發任務,都不會新開線程,只會在當前線程中執行任務。註意,如果當前線程是在一個串行隊列中執行,則不能在當前線程中向此隊列同步派發任務,否則會形成死鎖。
// Objective-C dispatch_sync(<#傳入隊列#>, ^{ //執行代碼塊 }); // SWIFT dispatch_sync(<#傳入隊列#>, { () -> Void in //執行代碼塊 })
2、異步派發(async)
異步派發不會阻塞當前線程。往非當前線程所在隊列異步派發任務時,系統一定會另起新線程;往當前線程所在隊列異步派發任務時有兩種情況:1、如果當前線程所在隊列是串行隊列,系統不會另起新線程,添加的任務會在隊列中前面的任務都執行完畢後才會執行(FIFO);2、如果當前線程所在隊列是並行隊列,系統會另起新線程,添加的任務會與隊列中的其他任務同時執行。
// Objective-C dispatch_async(<#傳入隊列#>, ^{ //執行代碼塊 }); // SWIFT dispatch_async(<#傳入隊列#>, { () -> Void in //執行代碼塊 })
隊列組
隊列組可以將不同類型的隊列添加到一個組裏,當所有隊列都執行完畢的時候有一個回調方法:
//Objective-C dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"隊列中的任務都已完成"); }); //SWIFT dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in NSLog("對了中的任務都已完成") }
其他實用方法
func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
:
當第一個參數傳入的是自定義的並行隊列時,添加的任務會阻塞這個隊列,當排在它前面的任務都執行完畢後再開始執行這個任務,並且當這個任務執行完畢後才會取消隊列的阻塞狀態;當第一個參數傳入值為其他情況,此方法的作用和dispatch_async一樣。
func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
:
當第一個參數傳入的是自定義的並行隊列時,這個方法和上一個方法作用類似,區別在於此方法還會阻塞當前線程;當第一個參數傳入值為其他情況,此方法的作用和dispatch_sync一樣。
NSOperation
NSOperation其實就是對GCD用面向對象的方式進行的封裝。
任務->NSOperation 隊列->NSOperationQueue
添加任務
NSOperation是一個基類,它有兩個子類:NSInvocationOperation
和 NSBlockOperation。創建任務後需要調用start方法來啟動任務,默認再當前隊列同步執行。可以在中途調用cancel方法中止任務的執行。
NSInvocationOperation
Objective-C
//1.創建對象 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationBlock) object:nil]; //2.開始執行 [operation start];
因為安全問題,Swift不能使用這個類
NSBlockOperation
//Objective-C //1.創建對象 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); }]; //2.開始任務 [operation start];
//--------------------------------------------------//
//SWIFT //1.創建對象 let operation = NSBlockOperation { () -> Void in println(NSThread.currentThread()) } //2.開始任務 operation.start()
那如何並行執行任務呢?NSBlockOperation
有一個方法:addExecutionBlock:可以給一個Opertion添加多個任務,這些任務會並行執行
Objective-C:
//1.創建NSBlockOperation對象 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); }]; //添加多個Block for (NSInteger i = 0; i < 5; i++) { [operation addExecutionBlock:^{ NSLog(@"第%ld次:%@", i, [NSThread currentThread]); }]; } //2.開始任務 [operation start];
SWIFT:
//1.創建NSBlockOperation對象 let operation = NSBlockOperation { () -> Void in NSLog("%@", NSThread.currentThread()) } //2.添加多個Block for i in 0..<5 { operation.addExecutionBlock { () -> Void in NSLog("第%ld次 - %@", i, NSThread.currentThread()) } } //3.開始任務 operation.start()
註意:addExecutionBlock
方法必須在 start()
方法之前執行,否則就會報錯。
自定義Operation
除了上面的兩種 Operation 以外,還可以自定義 Operation。繼承NSOperation
類,並實現其 main
方法,因為在調用 start
方法的時候,內部會調用 main
方法完成相關邏輯。除此之外,你還需要實現 cancel
在內的其他方法。
創建隊列
調用operation的start方法是同步執行的,也就是說它會阻塞當前線程,那怎麽用異步執行的方式呢?這裏就要使用隊列了NSOperationQueue
主隊列
這是系統定義的隊列,任何會刷新UI的任務都要在主隊列中進行。為了能給用戶流程的UI體驗,主隊列中的線程優先級是最高的。這個隊列中的任務都是串行執行的。
//OBJECTIVE-C NSOperationQueue *queue = [NSOperationQueue mainQueue]; //SWIFT let queue = NSOperationQueue.mainQueue()
其他隊列
自定義的隊列,就是其他隊列。默認情況下其他隊列的任務會在不同的線程中並行執行。它有一個參數maxConcurrentOperationCount最大並發數,用來設置最多能有多少個並行任務同時執行。把它設置成1就表明這是一個串行隊列。
//Objective-C 創建其他隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //Swift 創建其他隊列 let queue = NSOperationQueue()
添加任務
// Objective-C 往隊列裏添加任務 [queue addOperation:operation]; // Swift 往隊列裏添加任務 queue.addOperation(operation)
往隊列裏添加任務後會自動開始執行,不用調用start。
還有一個添加任務的方法-(void)addOperationWithBlock:(void (^)(void))block; 可以直接往
將想執行的代碼塊添加到隊列中了,不用再創建一個Operation。
添加依賴項
當任務A必須要在任務B完成後才能開始執行,這時就可以添加 A依賴B。
[operationA addDependency:operationB];
註意:1、不能相互添加依賴(A依賴B,B依賴A),這樣會形成死鎖;2、可以使用removeDependency來解除依賴;3、可以在不同隊列中的任務添加依賴。
其他方法
NSOperation
BOOL executing; //判斷任務是否正在執行 BOOL finished; //判斷任務是否完成 void (^completionBlock)(void); //用來設置完成後需要執行的操作 - (void)cancel; //取消任務 - (void)waitUntilFinished; //阻塞當前線程直到此任務執行完畢
NSOperationQueue
NSUInteger operationCount; //獲取隊列的任務數 - (void)cancelAllOperations; //取消隊列中所有的任務 - (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執行完畢 [queue setSuspended:YES]; // 暫停queue [queue setSuspended:NO]; // 繼續queue
線程同步
當有一個線程在對內存中的某一資源進行操作時,其他線程都不可以對這個資源進行操作,直到該線程完成操作。這樣做主要是為了保證一些資源數據的安全性和可靠性。
實現線程同步可以采用以下方式:
互斥鎖
//Objective-C @synchronized(self) { //需要執行的代碼塊 } //SWIFT objc_sync_enter(self) //需要執行的代碼塊 objc_sync_exit(self)
同步執行
可以使用上文介紹的串行隊列知識,把每一個要訪問此段資源的代碼都添加到同一個串行隊列中,這樣就可以實現線程同步。
總結
第一次寫博客,把自己以前用到的知識進行歸納總結,還是挺有用的。以後還會寫更多東西分享給大家,同時也方便自己查閱。
感謝簡書上的作者伯恩的遺產(http://www.jianshu.com/p/0b0d9b1f1f19)給了我很多幫助。
iOS多線程方案總結及使用詳解