GCD是什么:一句話負責線程池的中央調度
GCD全稱為Grand Central Dispatch,是libdispatch的市場名稱,而libdispatch是Apple的一個庫,其為并發代碼在iOS和OS X的多核硬件上執行提供支持。確切地說GCD是一套低層級的C API,通過 GCD,開發者只需要向隊列中添加一段代碼塊(block或C函數指針),而不需要直接和線程打交道。GCD在后端管理著一個線程池,它不僅決定著你的代碼塊將在哪個線程被執行,還根據可用的系統資源對這些線程進行管理。這樣通過GCD來管理線程,從而解決線程被創建的問題。
GCD的優勢
- 易用: GCD 提供一個易于使用的并發模型而不僅僅只是鎖和線程,以幫助我們避開并發陷阱,而且因為基于block,它能極為簡單得在不同代碼作用域之間傳遞上下文。
- 靈活: GCD 具有在常見模式上(比如鎖、單例),用更高性能的方法優化代碼,而且GCD能提供更多的控制權力以及大量的底層函數。
- 性能: GCD能自動根據系統負載來增減線程數量,這就減少了上下文切換以及增加了計算效率。
相關概念
GCD中涉及到隊列、線程、串行并行、同步異步、串行并發等概念,這些概念的認知可以通過我的另一篇文章
Dispatch Objects
盡管GCD是純C語言的,但它被組建成面向對象的風格。GCD對象被稱為dispatch object, 所有的dispatch objects都是OC對象.,就如其他OC對象一樣,當開啟了ARC(automatic reference counting)時,dispatch objects的retain和release都會自動執行。而如果是MRC的話,dispatch objects會使用dispatch_retain和dispatch_release這兩個方法來控制引用計數。Serial & Concurrent
串行任務就是每次只有一個任務被執行,并發任務就是在同一時間可以有多個任務被執行。Synchronous & Asynchronous
同步函數意思是在完成了它預定的任務后才返回,在任務執行時會阻塞當前線程。而異步函數則是任務會完成但不會等它完成,所以異步函數不會阻塞當前線程,會繼續去執行下一個函數。Concurrency & Parallelism
并發的意思就是同時運行多個任務。這些任務可能是以在單核 CPU 上以分時(時間共享)的形式同時運行,也可能是在多核 CPU 上以真正的并行方式來運行。然后為了使單核設備也能實現這一點,并發任務必須先運行一個線程,執行一個上下文切換,然后運行另一個線程或進程。并行則是真正意思上的多任務同時運行。Context Switch
Context Switch即上下文切換,一個上下文切換指當你在單個進程里切換執行不同的線程時存儲與恢復執行狀態的過程。這個過程在編寫多任務應用時很普遍,但會帶來一些額外的開銷。Dispatch Queues
GCD dispatch queues是一個強大的執行多任務的工具。Dispatch queue是一個對象,它可以接受任務,并將任務以先進先出(FIFO)的順序來執行。Dispatch queue可以并發的或串行的執行任意一個代碼塊,而且并發任務會像NSOperationQueue那樣基于系統負載來合適地并發進行,串行隊列同一時間則只執行單一任務。Dispatch queues內部使用的是線程,GCD 管理這些線程,并且使用Dispatch queues的時候,我們都不需要自己創建線程。Dispatch queues相對于和線程直接通信的代碼優勢是:Dispatch queues使用起來特別方便,執行任務更加有效率。
具體使用
隊列
對了除了串行和并行之分,全局隊列還有一些相關屬性影響著隊列內任務的執行,比如優先級等,高優先級的隊列,在等候被調度的情況下更容易獲取資源或機會
- 串行隊列
//主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//自定義的串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue0", DISPATCH_QUEUE_SERIAL);
- 并行隊列
//系統的全局并行隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//自定義的并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 串行并行的參數
DISPATCH_QUEUE_SERIAL //串行
DISPATCH_QUEUE_CONCURREN //并行
- 全局隊列的優先級,默認0
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
任務
- 同步任務
dispatch_sync(globalQueue, ^{
//同步任務
});
- 異步任務
dispatch_async(globalQueue, ^{
//異步任務
});
- dispatch_suspend != 立即停止隊列的運行
dispatch_suspend,dispatch_resume提供了“掛起、恢復”隊列的功能,簡單來說,就是可以暫停、恢復隊列上的任務。但是dispatch_suspend并不會立即暫停正在運行的block,而是在當前block執行完成后,暫停后續的block執行
其他使用
- DispatchGroup 任務組
很多時候我們需要等待一系列任務(block)執行完成,然后再做一些收尾的工作。如果是有序的任務,可以分步驟完成的,直接使用串行隊列就行。但是如果是一系列并行執行的任務,就需要DispatchGroup 任務組了
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQueue, ^{
//A
});
dispatch_group_async(group, globalQueue, ^{
//B
});
//當此group,此隊列中的所有任務都結束才執行此block
dispatch_group_notify(group, globalQueue, ^{
});
- dispatch_group_enter(group)、dispatch_group_leave(group)
DispatchGroup 任務組的并發任務的情況,如果此時拿不到隊列對象就沒法使用dispatch_group_async(group,隊列),怎么辦呢
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//Enter group
dispatch_group_enter(group);
[manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Deal with result...
//Leave group
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//Deal with error...
//Leave group
dispatch_group_leave(group);
}];
加結束任務
添加結束任務也可以分為兩種情況,如下:
在當前線程阻塞的同步等待: dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)。
添加一個異步執行的任務作為結束任務:dispatch_group_notify(group, globalQueue, ^{
});dispatch_barrier_async
同一個隊列,dispatch_barrier_async添加的任務會阻塞后面的任務,直至此任務執行結束;注意:只在自己創建的并發隊列上有效
dispatch_barrier_async(concurrentQueue, ^{
for (int i=0; i<5; i++) {
NSLog(@"=%@=====%d",[NSThread currentThread],i);
}
});
dispatch_async(concurrentQueue, ^{
for (int i=5; i<10; i++) {
NSLog(@"=%@=====%d",[NSThread currentThread],i);
}
});
- 延時執行 dispatch_after
//主隊列中的延時執行 時間單位是dispatch_time_t
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
//任意隊列中的延時執行
dispatch_after(DISPATCH_TIME_NOW+30, globalQueue, ^{
});
- 一次性代碼,單例常用
dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
//默認線程安全
});
- 遍歷 讓循環并行執行
dispatch_apply(10, globalQueue, ^(size_t index) {
//會執行10次 index順序不確定
});