swift開發多線程篇 - 多線程基礎
iOS 的三種多線程技術
- (1)NSThread
- 使用NSThread對象建立一個線程非常方便
- 但是!要使用NSThread管理多個線程非常困難,不推薦使用
- 技巧!使用[NSThread currentThread]跟蹤任務所在線程,適用於這三種技術
- (2) NSOperation/NSOperationQueue
- 是使用GCD實現的一套Objective-C的API
- 是面向對象的線程技術
- 提供了一些在GCD中不容易實現的特性,如:限制最大並發數量、操作之間的依賴關系
- (3) GCD —— Grand Central Dispatch
- 是基於C語言的底層API
- 用Block定義任務,使用起來非常靈活便捷
- 提供了更多的控制能力以及操作隊列中所不能使用的底層函數
CGD基本思想
- GCD的基本思想是就將操作s放在隊列s中去執行
- 操作使用Blocks定義
- 隊列負責調度任務執行所在的線程以及具體的執行時間
- 隊列的特點是先進先出(FIFO)的,新添加至對列的操作都會排在隊尾
- 提示
- GCD的函數都是以dispatch(分派、調度)開頭的
- 隊列
- dispatch_queue_t
- 串行隊列,隊列中的任務只會順序執行
- 並行隊列,隊列中的任務通常會並發執行
- 操作
- dispatch_async異步操作,會並發執行,無法確定任務的執行順序
- dispatch_sync 同步操作,會依次順序執行,能夠決定任務的執行順序
串行隊列
//dispatch_queue 是隊列名稱,在調試時輔助
var q =dispatch_queue_create("lllll",DISPATCH_QUEUE_SERIAL) //SERIAL 代表串行
dispatch_sync(q) { //sync 是同步
print("串行同步 %@", [NSThread.currentThread()]//同步操作不會新建線程、操作順序執行(沒用!)
}
dispatch_async(q) { //async 是異步
print("串行異步 %@", [NSThread.currentThread()]) //異步操作會新建線程、操作順序執行(非常有 用!)場景:既不影響主線程,又需要順序執行的操作!
}
並行隊列
var q =dispatch_queue_create("lllll",DISPATCH_QUEUE_CONCURRENT) //CONCURRENT 代表並行
dispatch_sync(q) { //sync 是同步
print("並行同步 %@", [NSThread.currentThread()] //同步操作不會新建線程、操作順序執行
}
dispatch_async(q) { //async 是異步
print("並行異步 %@", [NSThread.currentThread()]) //異步操作會新建多個線程、操作無序執行(有用,容易出錯!)隊列前如果有其他任務,會等待前面的任務完成之后再執行場景:既不影響主線程,又不需要順序執行的操作!
}
調整順序再運行
全局隊列
var q =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) //全局隊列是系統的,直接拿過來(GET)用就可以與並行隊列類似,但調試時,無法確認操作所在隊列
dispatch_sync(q) { //sync 是同步
print("全局同步 %@", [NSThread.currentThread(),i] //同步操作不會新建線程、操作順序執行
}
dispatch_async(q) { //async 是異步
print("全局異步 %@", [NSThread.currentThread()],i) //會新建多個線程、操作無序執行隊列前如果有其他任務,會等待前面的任務完成之后再執行
}
主隊列
var q =dispatch_get_main_queue() //每一個應用程序對應唯一一個主隊列,直接GET即可在多線程開發中,使用主隊列更新UI
dispatch_sync(q) {
print("主隊列同步 %@", [NSThread.currentThread()]) //如果把主線程中的操作看成一個大的Block,那么除非主線程被用戶殺掉,否則永遠不會結束主隊列中添加的同步操作永遠不會被執行,會死鎖
}
dispatch_async(q) {
print("主隊列異步 %@", [NSThread.currentThread()]) //主隊列中的操作都應該在主線程上順序執行的,不存在異步的概念
}
不同隊列中嵌套dispatch_sync的結果
// 全局隊列,都在主線程上執行,不會死鎖
var q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// 並行隊列,都在主線程上執行,不會死鎖
var q = dispatch_queue_create("lllll", DISPATCH_QUEUE_CONCURRENT)
// 串行隊列,會死鎖,但是會執行嵌套同步操作之前的代碼
var q = dispatch_queue_create("lllll", DISPATCH_QUEUE_SERIAL)
// 直接死鎖
var q = dispatch_get_main_queue()
dispatch_sync(q) {
print("同步任務 %@", [NSThread.currentThread()])
dispatch_sync(q) {
print("同步任務 %@", [NSThread.currentThread()])
}
}
串行隊列,同步任務,不需要新建線程
串行隊列,異步任務,需要一個子線程,線程的創建和回收不需要程序員參與!
“是最安全的一個選擇”串行隊列只能創建!
並行隊列,同步任務,不需要創建線程
並行隊列,異步任務,有多少個任務,就開N個線程執行,
無論什么隊列和什么任務,線程的創建和回收不需要程序員參與。
線程的創建回收工作是由隊列負責的
“並發”編程,為了讓程序員從負責的線程控制中解脫出來!只需要面對隊列和任務!
GCD階段性小結
- GCD
- 通過GCD,開發者不用再直接跟線程打交道,只需要向隊列中添加代碼塊即可
- GCD在后端管理着一個線程池,GCD不僅決定着代碼塊將在哪個線程被執行,它還根據可用的系統資源對這些線程進行管理。從而讓開發者從線程管理的工作中解放出來,通過集中的管理線程,緩解大量線程被創建的問題
- 使用GCD,開發者可以將工作考慮為一個隊列,而不是一堆線程,這種並行的抽象模型更容易掌握和使用
- GCD的隊列
- GCD公開有5個不同的隊列:運行在主線程中的主隊列,3 個不同優先級的后台隊列,以及一個優先級更低的后台隊列(用於 I/O)
- 自定義隊列:串行和並行隊列。自定義隊列非常強大,建議在開發中使用。在自定義隊列中被調度的所有Block最終都將被放入到系統的全局隊列中和線程池中
- 提示:不建議使用不同優先級的隊列,因為如果設計不當,可能會出現優先級反轉,即低優先級的操作阻塞高優先級的操作
NSOperation & NSOperationQueue
- 隊列及操作
- NSOperationQueue有兩種不同類型的隊列:主隊列和自定義隊列
- 主隊列運行在主線程上
- 自定義隊列在后台執行
- 隊列處理的任務是NSOperation的子類
- NSInvocationOperation
- NSBlockOperation
(1)NSOperation基本使用步驟:
- 定義操作隊列
- 定義操作
- 將操作添加到隊列
- 提示:一旦將操作添加到隊列,操作就會立即被調度執行
(2)NSOperationOperation(調度操作)
- 定義隊列
self.myQueue = [[NSOperationQueue alloc] init];
- 操作調用的方法
- (void)operationAction:(id)obj
{
NSLog(@"%@ - obj : %@", [NSThread currentThread], obj);
}
- 定義操作並添加到隊列
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationAction:) object:@(i)];
[self.myQueueaddOperation:op];
- 小結:需要准備一個被調度的方法,並且能夠接收一個參數
(3)NSBlockOperation(塊操作)
- 定義操作並添加到隊列
NSBlockOperation *op = [NSBlockOperationblockOperationWithBlock:^{
[self operationAction:@"Block Operation"];
}];
- 將操作添加到隊列
[self.myQueue addOperation:op];
- 小結:NSBlockOperation比NSInvocationOperation更加靈活
(4)設置同事並發的線程數量
[self.myQueue setMaxConcurrentOperationCount:2];//紅色字體代表設置同時並發的線程數量能夠有效地降低CPU和內存的開銷這 一功能用GCD不容易實現
for (int i = 0; i < 10; ++i) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[self operationAction:@(i)];
}];
[self.myQueue addOperation:op];
}
- 問題
塊代碼中的self為什么不會造成循環引用?
- AFN,底層用GCD開發,開發的接口是NSOperation的
多線程中的循環引用
- 如果self對象持有操作對象的引用,同時操作對象當中又直接訪問了self時,才會造成循環引用
- 單純在操作對象中使用self不會造成循環引用
- 注意:此時不能使用(weakSelf)
(1)多線程中的資源共享
- 並發編程中許多問題的根源就是在多線程中訪問共享資源。資源可以是一個屬性、一個對象、網絡設備或者一個文件等
- 在多線程中任何一個共享的資源都可能是一個潛在的沖突點,必須精心設計以防止這種沖突的發生
(2)互斥鎖(@synchronized)
單例和單例的實現步驟
單例:
- 通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源
- 如果希望系統中某個類的對象只能存在一個,單例模式是最好的解決方案
- iOS中最常見的單例就是UIApplication
- 應用場景:
- 音頻播放,背景音樂!
- 硬件資源:加速器、[UIScreen mainScreen]
- sharedXX, mainXXX
- 實現步驟:
- 重寫allocWithZone方法
- allocWithZone方法是對象分配內存空間時,最終會調用的方法,重寫該方法,保證只會分配一個內存空間
- 建立sharedXXX類方法,便於其他類訪問
+ (id)allocWithZone:(struct _NSZone *)zone
{
static Ticket *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
} //dispatch_once 是指線程安全的能夠做到在多線程的環境下Block中的代碼只會被執行一次
- 單例的優點與缺點
優點:可以阻止其他對象實例化單例對象的副本,從而確保所有對象都訪問唯一實例
缺點:單例對象一旦建立,對象指針是保存在靜態區的,單例對象在堆中分配的內存空間,會在應用程序終止后才會被釋放
提示:只有確實需要唯一使用的對象才需要考慮單例模式,不要濫用單例
NSObject的多線程方法
- 開啟后台執行任務的方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
- 在后台線程中通知主線程執行任務的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
- 獲取線程信息
[NSThreadcurrentThread]
- 線程休眠
[NSThreadsleepForTimeInterval:2.0f];
- 特點
- 使用簡單,量級輕
- 不能控制線程的數量以及執行順序
NSObject的多線程方法注意事項
- NSObject的多線程方法使用的是NSThread的多線程技術
- 而NSThread的多線程技術不會自動使用@autoreleasepool
- 在使用NSObject或NSThread的多線程技術時,如果涉及到對象分配,需要手動添加@autoreleasepool
@autoreleasepool
- 自動釋放池的工作原理
- 標記為autorelease的對象在出了作用域范圍后,會被添加到最近一次創建的自動釋放池中
- 當自動釋放池被銷毀或耗盡時,會向自動釋放池中的所有對象發送release消息
- 每個線程都需要有@autoreleasepool,否則可能會出現內存泄漏,但是使用NSThread多線程技術,並不會為后台線程創建自動釋放池
自動釋放池常見面試代碼
for (int i =0; i < 10; ++i) {
NSString *str =@"Hello World";
str = [str stringByAppendingFormat:@" - %d", i];
str = [str uppercaseString];
NSLog(@"%@", str);
}
