是異步執行任務的技術之一。它將應用程序中線程管理的代碼放在系統級中實現。開發者只須要定義想執行的任務並追加到適當的Dispatch Queue中,至於具體是哪一個線程執行、如何執行該任務,開發者不需管,交由GCD來管理。編程
所以GCD優於其餘異步技術的點:線程管理交由系統管理,效率更高。bash
源碼通過編譯連接後生成二進制目標代碼(CPU命令列),CPU順序執行這些命令列。 一個CPU核執行CPU命令列是一條無分叉的路徑,即爲線程。當一個線程被掛起時,當前CPU的寄存器等信息會保存到各自路徑專用的內存塊中(保存上下文),線程恢復繼續執行,則從內存塊中取出數據,復原CPU寄存器等信息,繼續執行切換路徑的CPU命令列(上下文切換)。多線程
多線程編程容易發生的問題:併發
多線程的優勢:app
總覽圖: 異步
Dispatch Queue:任務隊列,存儲等待執行的任務。它會按照追加順序(FIFO)執行任務async
串行隊列缺點:每一個串行隊列對應一個線程,當同時建立多個串行隊列時,對應多個線程。此時可能會發生數據競爭。 而一個並行隊列雖然同時會有多個線程,可是無論生成多少線程,都會有XNU內核來管理,因此不需擔憂串行隊列的問題。函數
// DISPATCH_QUEUE_SERIAL/NULL/0 串行隊列
// DISPATCH_QUEUE_CONCURRENT 並行隊列
// ARC狀況不須要手動釋放
dispatch_queue_t queue = dispatch_queue_create("queueIdentifier", 0);
dispatch_async(queue, ^{
NSLog(@"執行任務");
});
複製代碼
主隊列:oop
dispatch_queue_t mainQueue = dispatch_get_main_queue();
複製代碼
主隊列是在main()被調用前,自動被系統建立的串行隊列,和主線程相關聯。(但這並不表明主隊列中的任務只能被主線程執行)。ui
若是想要(其餘線程)執行主隊列中的任務,只能用如下三種方式中的一種:
全局並行隊列:Global Dispatch Queue
/*
#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_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
複製代碼
注意:使用函數本身建立的隊列的優先級和globalQueue中默認優先級是同等水平的。若是想要改變隊列的優先級須要使用函數dispatch_set_target_queue。
dispatch_set_target_queue(要改變的對象obj, 目標隊列tq)
複製代碼
給一個隊列設置目標隊列會改變這個隊列的某些行爲:
注意:設置目標隊列是,不要造成繼承循環。
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
dispatch_async(serialQ, ^{
NSLog(@"serialQ_1---------->");
});
dispatch_async(sQ3, ^{
NSLog(@"serialQ_3----------->");
});
dispatch_async(sQ2, ^{
NSLog(@"serialQ_2----------->");
});
此時結果是不定的,由於三個serialQueue是並行的。
複製代碼
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
dispatch_set_target_queue(serialQ, globalQ);
dispatch_set_target_queue(sQ2, serialQ);
dispatch_set_target_queue(sQ3, serialQ);
dispatch_async(serialQ, ^{
NSLog(@"serialQ_1---------->");
});
dispatch_async(sQ3, ^{
NSLog(@"serialQ_3----------->");
});
dispatch_async(sQ2, ^{
NSLog(@"serialQ_2----------->");
});
此時結果是肯定的 一、三、2。而且使用的是同一個線程
複製代碼
正常狀況下:不管是串行仍是併發隊列,只要設置了目標隊列,以後任務都會向上傳到它的上一級目標隊列中。所以任務其實最後都傳到根上去了。因此如今這條鏈上的隊列應該都是公用一個線程池的。所以若是要獲取當前隊列,即獲取執行當前代碼所關聯的隊列,結果是不定的。
GCD線程池中,系統最多會建立64個線程。手動建立的線程系統會默認設置目標隊列:全局默認優先級隊列。 即便是加入到併發隊列中的任務:設置必定時間後,將任務異步添加到隊列中(而不是將任務加到隊列中,必定時間後執行),所以時間可能不夠準確。
函數直接返回。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"執行任務");
});
複製代碼
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second); // 取浮點數的小數部分和整數部分
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC; // 小數部分轉納秒
milestone = dispatch_walltime(&time, 0);
return milestone;
}
複製代碼
當但願知道加入到隊列中的任務什麼時候所有執行完成時,有兩種方式:1. 將任務加入到串行隊列,結束的任務加到最後便可。2. 任務加到多個隊列中或者加到並行隊列中,須要用到group。
代碼解析:
// 將任務block異步加到隊列中,而且將block關聯到group羣組中,即block持有group
dispatch_group_async(group, globalQ, ^{
NSLog(@"global_blk0---->%@",[NSThread currentThread]);
});
// 當持有group的全部任務都執行完成後,會將任務加入到隊列中去執行。若是group中無任務,則當即將任務加到指定隊列中
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done---->%@",[NSThread currentThread]);
});
複製代碼
訪問數據時,容易出現數據競爭的問題。 寫操做不容許其餘讀寫操做並行執行,讀操做可與其餘讀操做並行執行。
須要dispatch_barrier_async爲須要單獨執行的操做加入柵欄。
代碼:
相似於前面的dispatch_wait,線程執行到該函數,會進入等待狀態,直到同步的任務結束,纔會返回,繼續執行。
容易形成的問題:死鎖
// 線程進入等待狀態,直到barrierBlock執行完成纔會返回
dispatch_barrier_sync(serialQ, ^{
});
複製代碼
dispatch_apply函數結合了dispatch_sync和dispatch group的功能。該函數按照指定次數將任務block添加到隊列中,並等待所有任務執行完成,再返回。
併發隊列:
串行隊列:應用:(不關心數據順序時)
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global, ^{
dispatch_apply([array count], global, ^(size_t i) {
NSLog(@"%@", array[i]);
});
});
複製代碼
注意:
① 1,2必須是一對一的,由於執行1,dispatch_object_t的suspension count會+1;執行2會減一。當count>0時,會一直處於掛起狀態。
② 當要暫停一個隊列時,上述函數僅對本身手動建立的隊列有效,對於系統隊列(global、main)無效的。
思想相似停車場:初始有n個車位,每來一輛車,先查看n是否大於0,若是n>0,車輛進入使用車位,n--。每輛車離開停車場n++。 當初始值設置爲1時,能夠做爲鎖來使用。
dispatch_once函數保證在整個應用程序執行中,只執行一次指定任務,即便在多線程環境下。
應用:單例模式
static NSObject *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[NSObject alloc] init];
});
return instance;
複製代碼
讀取較大文件時,可將其分割成合適大小併發讀取,提升讀取速度。