1. 進程與線程
- 進程概念: 進程是程序在計算機的一次執行活動,打開一個app,就開啟了一個進程,可包含多個線程
- 線程概念: 獨立執行的代碼段,一個線程同時間只能執行一次,反之多線程并發可以同一時間執行多個任務
- iOS程序中,主線程(又叫做UI線程) 主要任務是處理UI事件,顯示和刷新UI,(只有主線程有直接修改UI的能力) 耗時的操作放在子線程 (又叫做后臺線程,異步線程). 在iOS中開子線程去處理耗時的操作,可以有效的提升程序的執行效率,提高資源利用率,但是開啟線程會占用一定的內存, (主線程的堆棧大小是1M,第二個線程開始都是512KB,并且該值不能通過編譯器開關或線程API 函數來更改),降低程序的性能. 所以一般不要同時開很多線程
2. 線程相關
- 同步線程: 同步線程會阻塞當前線程去執行線程內的任務,執行完之后才會返回當前線程
- 串行隊列: 線程任務按先后順序逐個執行 (需要等待前面的任務執行完畢后再執行新的任務)
- 并發隊列: 多個任務按添加順序一起開始執行 (不用等待前面的任務執行完后再執行新的任務),但是添加間隔往往忽略不計,所以看起來像是儀器執行的
- 并發 &并行: 并行是基于多核設備的,并行不一定是并發的,并發不一定是并行的
- 死鎖: 兩個或者多個線程都要等待對方完成某個操作才能進行下一步,這時就會發生死鎖
*Thread Safe (線程安全) : 一段線程安全的代碼(對象), 可以同時被多個線程或并發的任務調度,不會產生問題, 非線程安全的只能按次序被訪問 - 所有Mutable對象都是非線程安全的,所有Immutable對象都是線程安全的,使用Mutable對象,一定要用同步鎖來同步訪問(@synchronized)
3. GCD的三種隊列類型
GCD編程的核心就是dispatch隊列, dispatch block 的執行最終都會放到某個隊列中去進行
The main queue (主線程串行隊列): 與主線程功能相同,提交至Main queue 的任務會在主線程中執行
Main queue 可以通過dispatch_get_main_queue()來獲取Global queue(全局并發隊列): 全局并發隊列由整個進程共享,有高 中(默認),低 后臺四個優先級別
Global queue 可以通過調用dispatch_get_global_queue 函數來獲取Custom queue (自定義隊列): 可以為串行,也可以為并發。
Custom queue 可以通過dispatch_queue_create()來獲取;Group queue (隊列組):將多線程進行分組,最大的好處是可獲知所有線程的完成情況。
Group queue 可以通過調用dispatch_group_create()來獲取,通過dispatch_group_notify,可以直接監聽組里所有線程完成情況。
4.The main queue(主線程串行隊列)
dispatch_sync 同步執行任務函數,不會開啟新的線程,dispatch_async 異步執行任務函數,會開啟新的線程
- 獲取主線程串行隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
2.主線程串行隊列同步執行任務,在主線程運行時,會產生死鎖
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue,^{
NSLog("MainQueue");
});
程序一直處于等待狀態,block中的代碼將執行不到
3.主線程串行隊列異步執行任務,在主線程運行,不會產生死鎖。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
NSLog("MainQueue");
});
程序正常運行,block中的代碼正常運行
4.從子線程,異步返回主線程更新UI<這種使用方式比較多>
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
//子線程異步執行下載任務,防止主線程卡頓
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
NSError *error;
NSString *htmlData = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (htmlData != nil) {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//異步返回主線程,根據獲取的數據,更新UI
dispatch_async(mainQueue, ^{
NSLog(@"根據更新UI界面");
});
} else {
NSLog(@"error when download:%@",error);
}
});
主線程串行隊列由系統默認生成的,所以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。
5. Global queue (全局并發隊列)
耗時的操作,比如讀取網絡數據,IO,數據庫讀寫等, 我們會在另一個線程中處理這些操作, 然后通知主線程更新界面
1.獲取全局并發隊列
//程序默認的隊列級別,一般不要修改,
DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //HIGH dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); //LOW dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //BACKGROUND dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
2.全局并發隊列同步執行任務,在主線程會導致頁面卡頓
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_sync(globalQueue, ^{ sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");
依次輸出: "current task" "sleep 2.0s","next task",2s之后才會執行block后面的代碼,會造成頁面卡頓
- 全局并發隊列異步執行任務,在主線程運行,會開啟新的子線程取執行任務,頁面不會卡頓
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ //異步執行,不會等待2s ,造成卡頓 sleep(2.0); NSLog(@"sleep 2.0s"); }); NSLog(@"next task");
4.多個全局并發隊列,異步執行任務
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"current task"); dispatch_async(globalQueue, ^{ NSLog(@"最先加入全局并發隊列"); }); dispatch_async(globalQueue, ^{ NSLog(@"次加入全局并發隊列"); }); NSLog(@"next task");
異步線程的執行順序是不確定的,幾乎同步執行,全局并發隊列有系統默認生成,,所以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。
6.自定義隊列(custom queue)
1.自定義串行隊列
獲取自定義串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("MrRightGen.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"%s",dispatch_queue_get_label(conCurrentQueue)) ;
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)函數中第一個參數是給這個queue起的標識,這個在調試的可以看到是哪個隊列在執行,或者在crash日志中,也能做為提示。第二個是需要創建的隊列類型,是串行的還是并發的自定義串行隊列同步執行任務
dispatch_queue_t serialQueue = dispatch_queue_create("MrRightGen.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"current task"); dispatch_sync(serialQueue, ^{ NSLog(@"最先加入自定義串行隊列"); sleep(2); }); dispatch_sync(serialQueue, ^{ NSLog(@"次加入自定義串行隊列"); }); NSLog(@"next task");
當前線程等待串行隊列中的子線程執行完成之后再執行,串行隊列中先進來的子線程先執行任務,執行完成后,再執行隊列中后面的任務。自定義串行隊列嵌套執行同步任務,產生死鎖
dispatch_queue_t serialQueue = dispatch_queue_create("MrRightGen.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(serialQueue, ^{ //該代碼段后面的代碼都不會執行,程序被鎖定在這里 NSLog(@"會執行的代碼"); dispatch_sync(serialQueue, ^{ NSLog(@"代碼不執行"); }); });
異步執行串行隊列,嵌套同步執行串行隊列,同步執行的串行隊列中的任務將不會被執行,其他程序正常執行
dispatch_queue_t serialQueue = dispatch_queue_create("MrRightGen.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ NSLog(@"會執行的代碼"); dispatch_sync(serialQueue, ^{ NSLog(@"代碼不執行"); }); });
注意不要嵌套使用同步執行的串行隊列任務
- 自定義并發隊列
獲取自定義并發隊列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("MrRightGen.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
*自定義并發執行同步任務
dispatch_queue_t conCurrentQueue = dispatch_queue_create("MrRightGen.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"current task"); dispatch_sync(conCurrentQueue, ^{ NSLog(@"先加入隊列"); }); dispatch_sync(conCurrentQueue, ^{ NSLog(@"次加入隊列"); }); NSLog(@"next task");
//任務自上而下依次執行自定義并發隊列嵌套執行同步任務(不會產生死鎖,程序正常運行)
dispatch_queue_t conCurrentQueue = dispatch_queue_create("MrRightGen.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"先加入隊列");
dispatch_sync(conCurrentQueue, ^{
NSLog(@"次加入隊列");
});
});
NSLog(@"next task");自定義并發隊列執行異步任務
dispatch_queue_t conCurrentQueue = dispatch_queue_create("MrRightGen.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_async(conCurrentQueue, ^{
NSLog(@"先加入隊列");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"次加入隊列");
});
NSLog(@"next task");
異步執行任務,開啟新的子線程,不影響當前線程任務的執行,并發隊列中的任務,幾乎是同步執行的,輸出順序不確定
6. 隊列組 (Group queue)
當遇到需要多個線程并發執行,然后等多個線程都結束之后,再匯總執行結果時,可以用group queue
1.使用場景: 同時下載多個圖片,所有圖片下載完成之后,去更新UI (需要回到主線程) 或者去處理其他任務 (可以是其他線程隊列)
2.原理: 使用函數 dispatch_group_create 創建 dispatch group , 然后使用函數 dispatch_group_async 來將要執行的block 任務提交到一個 dispatch queue. 同時將他們添加到一個組, 等要執行的block 任務全部執行完畢之后,使用 dispatch_group_notify 函數接收完成時的消息
3.使用示例:
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_group_t groupQueue = dispatch_group_create(); NSLog(@"current task"); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ NSLog(@"并行任務1"); }); dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{ NSLog(@"并行任務2"); }); dispatch_group_notify(groupQueue, mainQueue, ^{ NSLog(@"groupQueue中的任務 都執行完成,回到主線程更新UI"); }); NSLog(@"next task");
按此順序依次輸出
4.在當前線程阻塞的同步等待dispatch_group_wait
dispatch_group_t groupQueue = dispatch_group_create();
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
long isExecuteOver = dispatch_group_wait(groupQueue, delayTime);
if (isExecuteOver) {
NSLog(@"wait over");
} else {
NSLog(@"not over");
}
NSLog(@"并行任務1");
});
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任務2");
});
參數注釋:
第一個參數一般是DISPATCH_TIME_NOW,表示從現在開始
第二個參數是延時的具體時間
延時1秒可以寫成如下幾種:
NSEC_PER_SEC----每秒有多少納秒
dispatch_time(DISPATCH_TIME_NOW, 1NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在納秒的基礎上)
dispatch_time(DISPATCH_TIME_NOW, 1000USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少納秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---納秒
7.GCD中一些系統提供的常用dispatch方法
1.dispatch_after 延時添加到隊列
dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC); dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC); dispatch_queue_t mainQueue = dispatch_get_main_queue(); NSLog(@"current task"); dispatch_after(delayTime3, mainQueue, ^{ NSLog(@"3秒之后添加到隊列"); }); dispatch_after(delayTime2, mainQueue, ^{ NSLog(@"2秒之后添加到隊列"); }); NSLog(@"next task");
dispatch_after 只是延時提交block,并不是延時后立即執行,并不能做到精準控制
- dispatch_once 保證在app運行期間,block中的代碼只執行一次
經典使用場景--單例
//GCD實現單例功能
+ (ShareManager *)shareManager
{
static ShareManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[self alloc] init];
});
return sharedManager;
}
@end
ShareManager的使用
#######import "ShareManager.h"
在需要使用的函數中,直接調用下面的方法
ShareManager *share = [ShareManager shareManager];
NSLog(@"share is %@",share.someProperty);
最近在學習多線程與GCD的使用,看了很多資料,感覺本文寫的比較詳細,故引用之.參考自dullgrass-簡書