2015/12/09程序員
Day 46編程
今天學習多線程swift
多線程的優缺點後端
新建線程會消耗內存空間和CPU時間,線程太多會下降系統的運行性能設計模式
iOS的三種多線程技術安全
GCD基本思想網絡
GCD的函數都是以dispatch(分派、調度)開頭的多線程
dispatch_queue_t 閉包
dispatch_async 異步操做,會併發執行,沒法肯定任務的執行順序併發
dispatch_sync 同步操做,會依次順序執行,可以決定任務的執行順序
下面是演練GCD的OC代碼
串行隊列(一個接一個,排隊跑步,保持隊形)
1 NSLog(@"%@", [NSThread currentThread]); 2 dispatch_queue_t q = dispatch_queue_create(「yu1」, DISPATCH_QUEUE_SERIAL); 3 // 非ARC開發時,千萬別忘記release 4 // dispatch_release(q); 5 6 // 串行行隊列的同步任務,一樣會在主線程上運行 7 // 提示:在開發中極少用 8 for (int i = 0; i < 5; ++i) { 9 // 同步任務順序執行 10 dispatch_sync(q, ^{ 11 NSLog(@"%@ %d", [NSThread currentThread], i); 12 }); 13 } 14 15 for (int i = 0; i < 5; ++i) { 16 // 異步任務,併發執行,可是若是在串行隊列中,仍然會依次順序執行 17 dispatch_async(q, ^{ 18 // [NSThread currentThread] 能夠在開發中,跟蹤當前線程 19 // num = 1,表示主線程 20 // num = 2,表示第2個子線程。。。 21 NSLog(@"%@ %d", [NSThread currentThread], i); 22 }); 23 }
打印結果以下
而後是並行隊列(並排跑,相似於賽跑)
1 NSLog(@"%@", [NSThread currentThread]); 2 // 特色:沒有隊形,執行順序程序員不能控制! 3 // 應用場景:併發執行任務,沒有前後順序關係 4 // 並行隊列容易出錯!並行隊列不能控制新建線程的數量! 5 dispatch_queue_t q = dispatch_queue_create("yu2", DISPATCH_QUEUE_CONCURRENT); 6 7 for (int i = 0; i < 10; ++i) { 8 // 異步任務 9 dispatch_async(q, ^{ 10 // [NSThread currentThread] 能夠在開發中,跟蹤當前線程 11 // num = 1,表示主線程 12 // num = 2,表示第2個子線程。。。 13 NSLog(@"%@ %d", [NSThread currentThread], i); 14 }); 15 } 16 17 for (int i = 0; i < 10; ++i) { 18 // 同步任務順序執行 19 dispatch_sync(q, ^{ 20 NSLog(@"%@ %d", [NSThread currentThread], i); 21 }); 22 }
控制檯打印
注意:關於多線程不要只看一次運行結果,像上面的代碼,多運行幾回結果是不同的
全局隊列(蘋果爲了方便多線程的設計,提供一個全局隊列,供全部的APP共同使用)
1 // 全局隊列與並行隊列的區別 2 // 1> 不須要建立,直接GET就能用 3 // 2> 兩個隊列的執行效果相同 4 // 3> 全局隊列沒有名稱,調試時,沒法確認準確隊列 5 NSLog(@"%@", [NSThread currentThread]); 6 // 優先級在開發中通常用DISPATCH_QUEUE_PRIORITY_DEFAULT 7 dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 8 9 for (int i = 0; i < 5; ++i) { 10 // 同步任務順序執行 11 dispatch_sync(q, ^{ 12 NSLog(@"%@ %d", [NSThread currentThread], i); 13 }); 14 } 15 16 for (int i = 0; i < 5; ++i) { 17 dispatch_async(q, ^{ 18 // [NSThread currentThread] 能夠在開發中,跟蹤當前線程 19 // num = 1,表示主線程 20 // num = 2,表示第2個子線程。。。 21 NSLog(@"%@ %d", [NSThread currentThread], i); 22 }); 23 }
打印以下
主(線程)隊列,保證操做在主線程上執行
1 // 每個應用程序都只有一個主線程 2 // 爲何須要在主線程上工做呢? 3 // 在iOS開發中,全部UI的更新工做,都必須在主線程上執行! 4 dispatch_queue_t q = dispatch_get_main_queue(); 5 6 // 主線程是有工做的,並且除非將程序殺掉,不然主線程的工做永遠不會結束! 7 // 同步任務,阻塞了!!! 8 // dispatch_sync(q, ^{ 9 // NSLog(@"come here baby!"); 10 //}); 11 12 // 異步任務,在主線程上運行,同時是保持隊形的 13 for (int i = 0; i < 5; ++i) { 14 dispatch_async(q, ^{ 15 NSLog(@"%@ - %d", [NSThread currentThread], i); 16 }); 17 }
注意阻塞狀況,打印以下
接下來用swift將上面4個demo演練了一遍
串行隊列(一個接一個,排隊跑步,保持隊形)
let q = dispatch_queue_create("串行隊列1", DISPATCH_QUEUE_SERIAL) print("串行隊列,同步任務") for i in 0 ..< 5 { //同步任務,順序執行,同一線程上執行(仍是在主線程) dispatch_sync(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) } print("串行隊列,異步任務") for i in 0 ..< 5 { //異步任務,開一個子線程併發執行,可是若是在串行隊列中,仍然會依次順序執行 // num = 1,表示主線程 // num = 2,表示第2個子線程。。。 dispatch_async(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) }
控制檯打印
並行隊列(並排跑,相似於賽跑)
// 特色:沒有隊形,執行順序程序員不能控制! // 應用場景:併發執行任務,沒有前後順序關係 // 並行隊列容易出錯!並行隊列不能控制新建線程的數量! let q = dispatch_queue_create("並行隊列1", DISPATCH_QUEUE_CONCURRENT) print("並行隊列,異步任務") for i in 0 ..< 5 { //異步任務,隨機亂序,而且有N個子線程 dispatch_async(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) } print("並行隊列,同步任務") for i in 0 ..< 5 { //同步任務,順序執行,同一線程上執行(仍是在主線程) dispatch_sync(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) }
控制檯打印
全局隊列(蘋果爲了方便多線程的設計,提供一個全局隊列,供全部的APP共同使用)
// 全局隊列與並行隊列的區別 // 1> 不須要建立,直接GET就能用 // 2> 兩個隊列的執行效果相同 // 3> 全局隊列沒有名稱,調試時,沒法確認準確隊列 // 優先級在開發中通常用DISPATCH_QUEUE_PRIORITY_DEFAULT let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) print("全局隊列,異步任務") for i in 0 ..< 5 { //異步任務,隨機亂序,而且有N個子線程 dispatch_async(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) } print("全局隊列,同步任務") for i in 0 ..< 5 { //同步任務,順序執行,同一線程上執行(仍是在主線程) dispatch_sync(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) }
控制檯打印
主(線程)隊列,保證操做在主線程上執行
// 每個應用程序都只有一個主線程 // 爲何須要在主線程上工做呢? // 在iOS開發中,全部UI的更新工做,都必須在主線程上執行! let q = dispatch_get_main_queue() print("主隊列,異步任務") for i in 0 ..< 5 { // 異步任務,在主線程上運行,同時是保持隊形的 dispatch_async(q, { () -> Void in print("\(NSThread.currentThread()) - \(i)") }) } // print("主隊列,同步任務,阻塞") // // 主線程是由工做的,並且除非將程序殺掉,不然主線程的工做永遠不會結束! // // 阻塞了!!! // dispatch_sync(q, { () -> Void in // print("come on baby") // })
控制檯打印
dispatch_sync的應用場景
1 dispatch_queue_t q = dispatch_queue_create(「yu3」, DISPATCH_QUEUE_CONCURRENT); 2 __block BOOL logon = NO; 3 dispatch_sync(q, ^{ 4 NSLog(@"模擬耗時操做 %@", [NSThread currentThread]); 5 [NSThread sleepForTimeInterval:2.0f];//一般在多線程調試中用於模擬耗時操做, 在發佈的應用程序中,不要使用此方法! 6 NSLog(@"模擬耗時完成 %@", [NSThread currentThread]); 7 logon = YES; 8 }); 9 10 dispatch_async(q, ^{ 11 NSLog(@"登陸完成的處理 %@", [NSThread currentThread]); 12 });
GCD小結
串行隊列,同步任務,不須要新建線程
串行隊列,異步任務,須要一個子線程,線程的建立和回收不須要程序員參與!
「是最安全的一個選擇」串行隊列只能建立一個子線程
並行隊列,同步任務,不須要建立線程
並行隊列,異步任務,有多少個任務,就開N個線程執行,
不管什麼隊列和什麼任務,線程的建立和回收不須要程序員參與。
線程的建立回收工做是由隊列負責的
「併發」編程,爲了讓程序員從負責的線程控制中解脫出來!只須要面對隊列和任務!
GCD隊列示意圖
NSOperation & NSOperationQueue
NSOperation的基本使用步驟
提示:一旦將操做添加到隊列,操做就會當即被調度執行
NSInvocationOperation(調度操做)
1 //定義隊列 2 self.myQueue = [[NSOperationQueue alloc] init]; 3 //操做調用的方法 4 - (void)operationAction:(id)obj 5 { 6 NSLog(@"%@ - obj : %@", [NSThread currentThread], obj); 7 } 8 //定義操做並添加到隊列 9 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction:) object:@(i)]; 10 [self.myQueue addOperation:op]; 11 //小結:須要準備一個被調度的方法,而且可以接收一個參數,使用起來不方便
NSBlockOperation
1 //定義操做並添加到隊列 2 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ 3 [self operationAction:@"Block Operation"]; 4 }]; 5 //將操做添加到隊列 6 [self.myQueue addOperation:op];
設置操做的依賴關係(順序執行)
1 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ 2 NSLog(@"%@ - 下載圖片", [NSThread currentThread]); 3 }]; 4 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ 5 NSLog(@"%@ - 添加圖片濾鏡", [NSThread currentThread]); 6 }]; 7 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ 8 NSLog(@"%@ - 更新UI", [NSThread currentThread]); 9 }]; 10 [op2 addDependency:op1]; 11 [op3 addDependency:op2]; 12 [self.myQueue addOperation:op1]; 13 [self.myQueue addOperation:op2]; 14 [[NSOperationQueue mainQueue] addOperation:op3];
提示:利用addDependency能夠指定操做之間的彼此依賴關係(執行前後順序)
注意:不要出現循環依賴!
NSOperationQueue還能夠設置同時併發的線程數量
[self.myQueue setMaxConcurrentOperationCount:3];
設置同時併發的線程數量可以有效地下降CPU和內存的開銷
這一功能用GCD不容易實現
Run Loop
(1) Run Loop提供了一種異步執行代碼的機制,不能並行執行任務
(2) 在主隊列中,Main Run Loop直接配合任務的執行,負責處理UI事件、計時器,以及其它內核相關事件
(3) Run Loop的主要目的是保證程序執行的線程不會被系統終止
Run Loop的工做特色
主線程和其餘線程中的Run Loop
多線程中的資源共享
併發編程中許多問題的根源就是在多線程中訪問共享資源。資源能夠是一個屬性、一個對象、網絡設備或者一個文件等
在多線程中任何一個共享的資源均可能是一個潛在的衝突點,必須精心設計以防止這種衝突的發生
爲了保證性能,atomic僅針對屬性的setter方法作了保護
而爭搶共享資源時,若是涉及到屬性的getter方法,可使用互斥鎖@synchronized能夠保證屬性在多個線程之間的讀寫都是安全的
不管是atomic仍是@synchronized,使用的代價都是高昂的,不建議使用
建議:多線程是併發執行多個任務提升效率的,若是可能,應該在線程中避免爭搶共享資源
正是出於性能的考慮,UIKit中的絕大多數的類都不是線程安全的,所以,蘋果公司要求:更新UI相關的操做,應該在主線程中執行
單例模式
音頻播放,背景音樂!
硬件資源:加速器、[UIScreen mainScreen]
下面是實現單例模式的OC代碼
DemoObj.h
1 @interface DemoObj : NSObject 2 3 + (instancetype)sharedDemoObj; 4 5 @end
DemoObj.m
1 @implementation DemoObj 2 /** 3 1. 重寫allocWithZone,用dispatch_once實例化一個靜態變量(dispatch_once是線程安全的,可以作到在多線程的環境下Block中的代碼只會被執行一次) 4 2. 寫一個+sharedXXX方便其餘類調用 5 */ 6 // 在OC中,全部對象的內存空間的分配,最終都會調用allocWithZone方法 7 // 若是要作單例,須要重寫此方法 8 // GCD提供了一個方法,專門用來建立單例的 9 + (instancetype)allocWithZone:(struct _NSZone *)zone { 10 static DemoObj *result; 11 static dispatch_once_t onceToken; 12 dispatch_once(&onceToken, ^{ 13 result = [super allocWithZone:zone]; 14 }); 15 return result; 16 } 17 18 + (instancetype)sharedDemoObj { 19 return [[self alloc] init]; 20 } 21 @end
Swift代碼
class Singleton: NSObject { internal class func sharedInstance() -> Singleton { struct Static { static var onceToken : dispatch_once_t = 0 static var instance : Singleton? = nil } dispatch_once(&Static.onceToken) { Static.instance = Singleton() } return Static.instance! } }
單例小結
優勢: 能夠阻止其餘對象實例化單例對象的副本,從而確保全部對象都訪問惟一實例
缺點: 單例對象一旦創建,對象指針是保存在靜態區的,單例對象在堆中分配的內存空間,會在應用程序終止後纔會被釋放
提示: 只有確實須要惟一使用的對象才須要考慮單例模式,不要濫用單例
NSObject的多線程方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
[NSThread currentThread]
[NSThread sleepForTimeInterval:2.0f];
使用簡單,量級輕
不能控制線程的數量以及執行順序
注意事項
NSObject的多線程方法使用的是NSThread的多線程技術
而NSThread的多線程技術不會自動使用@autoreleasepool
在使用NSObject或NSThread的多線程技術時,若是涉及到對象分配,須要手動添加@autoreleasepool
複習一下autoreleasepool
iOS開發中的內存管理
自動釋放池的工做原理
每一個線程都須要有@autoreleasepool,不然可能會出現內存泄漏,可是使用NSThread多線程技術,並不會爲後臺線程建立自動釋放池