程序與線程概念
--一個運作的程式就是一個程序或者叫做一個任務
--一個程序至少包含一個線程,線程是程式的執行流
--iOS程式啟動時,在建立一個程序的同時, 會開始運作一個線程,該線程被稱為主線程
--主線程是其他線程最終的父線程,所有界面的顯示操作必須在主線程進行!!!
--系統中的每一個程序都有自己獨立的虛拟記憶體空間,而同一個程序中的多個線程則共用程序的記憶體空間
--每建立一個新的線程,都會消耗一定記憶體和CPU時間
--當多個線程對同一個資源出現争奪的時候需要注意線程安全問題
多線程的優勢與難點
--優勢
*充分發揮多核處理器優勢,将不同線程任務配置設定給不同的處理器,真正進入“并行運算”狀态
*将耗時、輪詢或者并發需求高等任務配置設定到其他線程執行,并由主線程負責統一更新界面會使得應用程式更加流暢,使用者體驗更好
*當硬體處理器的數量增加,程式會運作更快,而無需做任何調整
--難點
*共享資源的“争奪”
*多線程是為了同步完成多項任務,不是為了提高運作效率,而是為了通過提高資源使用效率來提高系統的整體性能
多線程使用注意事項
--線程使用不是無節制的
*iOS中的主線程的堆棧大小是1M
*從第二個線程開始都是512KB
*這些數值不能通過編譯器開關或線程API函數更改
--隻有主線程有直接修改UI的能力
iOS的三種多線程技術
1.NSThread
2.NSOperation
3.GCD —— Grand Central Dispatch,是基于C語言的架構
以上這三種程式設計方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡單,也是Apple最推薦使用的。但是就目前而言,iOS的開發者,需要了解三種多線程技術的基本使用過程。因為很多架構技術分别使用了不同多線程技術。例如NSURLConnection的異步靜态方法:
sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
三種多線程技術的對比
NSThread:
優點:NSThread 比其他兩個輕量級,使用簡單
缺點:需要自己管理線程的生命周期、線程同步。線程同步對資料的加鎖會有一定的系統開銷
NSOperation:
不需要關心線程管理,資料同步的事情,可以把精力放在自己需要執行的操作上
GCD:
Grand Central Dispatch是由蘋果開發的一個多核程式設計的解決方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和強大的技術
NSThread
建立線程方法:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
參數說明:
--selector:線程執行的方法,隻能有一個參數,不能有傳回值
--target:selector消息發送的對象
--argument:傳輸給target的唯一參數,也可以是nil
NSObject直接加入了多線程的支援,允許對象的某個方法在背景運作(本方法普遍應用在遊戲中,利用多線程播放聲音)
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
代碼演練
// 建立線程1
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(threadSaleMethod) object:nil];
// 設定線程名稱,因為需要通過線程名稱跟蹤線程執行情況,是以此處不使用線程靜态方法
[thread1 setName:@"售票線程-1"];
// 啟動線程1
[thread1 start];
// 建立線程2,步驟同上
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(threadSaleMethod) object:nil];
[thread2 setName:@"售票線程-2"];
[thread2 start];
NSThread使用注意
*當涉及到共享資源争奪時,共享資源的資料加鎖是一個難點,既要保證資料安全,又要保證線程執行效率
--使用前加鎖
--盡快使用
--使用完解鎖
--再去做其他的事情
*多線程編寫順序
1.單個方法調試OK
2.單個線程調試OK
3.增加線程,并考慮線程加鎖、解鎖的準确位置。
注意:
--隻有主線程能夠修改UI
--如果不涉及到記憶體争搶,NSThread寫多線程是最簡單的
*當不涉及共享資源争奪時,使用NSObject的performSelectorInBackground方法可以非常的友善地實作多線程
NSOperation & NSOperationQueue
*NSOperation的兩個子類
1.NSInvocationOperation
2.NSBlockOperation
*工作原理:
--用NSOperation封裝要執行的操作
--将建立好的NSOperation對象放NSOperationQueue中
--啟動OperationQueue開始新的線程執行隊列中的操作
*注意事項:
--使用多線程時通常需要控制線程的并發數,因為線程會消耗系統資源,同時運作的線程過多,系統會變慢
--使用以下方法可以控制并發的線程數量:
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
NSOperation操作流程
1.定義操作
2.定義隊列
3.将操作添加至隊列,隊列是自動啟動的
代碼演練
while (YES) {
if (_tickets > 0) {
// 在主線程操作隊列更新界面
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSString *str = [NSString stringWithFormat:@"目前票數:%d,售票線程:%@", _tickets, operationName];
[self appendTextView:str];
_tickets--;
}];
// 模拟休息
// ……
} else {
// 在主線程操作隊列更新界面
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSString *str = [NSString stringWithFormat:@"票已售完,售票線程:%@", operationName];
[self appendTextView:str];
}];
break;
}
}
******NSInvocationOperation代碼*********
// 設定預售票數,共享資源!
_tickets = 20;
// 定義操作1
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationSaleMethod:) object:@"售票操作-1"];
// 定義操作2
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationSaleMethod:) object:@"售票操作-2"];
// 定義操作隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operation2];
******NSBlockOperation代碼*********
// 設定預售票數,共享資源!
_tickets = 20;
// 定義操作隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
[self operationSaleMethod:@"售票操作-1"];
}];
[queue addOperationWithBlock:^{
[self operationSaleMethod:@"售票操作-2"];
}];
NSOperation使用注意
--NSOperation中無需使用線程鎖
--除更新UI之外,對共享資源的争奪也需放在主線程隊列之中
--将定義的操作添加至隊列之後,多線程便開始啟動
--NSBlockOperation的使用相比NSInvocationOperation更加靈活、友善
--通過setMaxConcurrentOperationCount方法可以控制并發的最大線程數量
GCD
*GCD是基于C語言的架構
*工作原理:
--讓程式平行排隊的特定任務,根據可用的處理資源,安排它們在任何可用的處理器上執行任務
--要執行的任務可以是一個函數或者一個block
--底層是通過線程實作的,不過程式員可以不必關注實作的細節
--GCD中的FIFO隊列稱為dispatch queue,可以保證先進來的任務先得到執行
dispatch_notify可以實作監聽一組任務是否完成,完成後得到通知
*dispatch queue:
--Main dispatch queue:是全局可用的隊列,用于在主線程上執行任務
--Serial:同時隻執行一個任務
--Concurrent:可以并發地執行多個任務,但是執行完成的順序是随機的
GCD使用流程
1. 擷取全局排程隊列
2. 建立排程群組
3. 向排程群組添加異步任務,并指定執行隊列
4. 接收群組排程完成通知,群組中所有任務完成後獲得通知
代碼演練
while (YES) {
if (_tickets > 0) {
// 在主排程隊列更新界面
dispatch_async(dispatch_get_main_queue(), ^{
NSString *str = [NSString stringWithFormat:@"目前票數:%d,售票線程:%@", _tickets, gcdName];
[self appendTextView:str];
_tickets--;
});
// 模拟休息
if ([gcdName isEqualToString:@"售票GCD-1"]) {
[NSThread sleepForTimeInterval:0.1f];
} else {
[NSThread sleepForTimeInterval:0.2f];
}
} else {
break;
}
}
// 設定預售票數,共享資源!
_tickets = 20;
// 1. 擷取預設排程優先級的全局排程隊列,第二個參數為今後拓展使用,目前始終傳入0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 建立任務排程組
dispatch_group_t group = dispatch_group_create();
// 3. 排程群組異步任務
dispatch_group_async(group, queue, ^{
[self gcdSaleMethod:@"售票GCD-1"];
});
dispatch_group_async(group, queue, ^{
[self gcdSaleMethod:@"售票GCD-2"];
});
// 4. 接收群組排程通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self appendTextView:@"票已售完"];
});
GCD使用注意
--GCD的原理和Operation非常相像,隻是它是C語言架構的。
--GCD中無需使用線程鎖
--除更新UI之外,對共享資源的争奪也需放在主排程隊列之中
--将任務添加至群組,并指定全局操作隊列,使得GCD的多線程更加靈活、友善
--dispatch_group_notify可以監聽一組任務是否完成。這個方法很有用,比如你執行三個下載下傳任務,當三個任務都下載下傳完成後,才通知界面說已經完成
--如果不需要監聽一組任務,可以直接使用dispatch_async方法