什麼是GCD
Grand Central Dispatch(GCD)是非同步執行任務的技術之一。一般將應用程式中記述的執行緒管理用的程式碼在系統級中實現。開發者只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的執行緒並計劃執行任務。由於執行緒管理是作為系統的一部分來實現的,因此可統一管理,也可執行任務,這樣就比以前的執行緒更有效率。
Dispatch Queue
將想執行的任務追加到適當的Dispatch Queue
Dispatch Queue種類 | 說明 |
---|---|
Serial Dispatch Queue | 序列佇列,任務按照追加順序處理(FIFO) |
Concurrent Dispatch Queue | 並行佇列 |
序列佇列
並行佇列
重點
序列佇列只有一個執行緒,並行佇列有多個執行緒。
自建Queue
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);//建立了一個並行佇列
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);//建立了一個序列佇列
複製程式碼
系統Queue
- Main DispatchQueue
在主執行緒中執行的Dispatch Queue。因為主執行緒只有1個,所以
Main Dispatch Queue
是序列佇列。加入到主佇列中的任務一定不會生成新的執行緒,因為主佇列必須有且只有一條主執行緒。 - Global Dispatch Queue 一個所有應用程式都能夠使用的併發佇列。加入到該佇列中的任務不一定會生成執行緒。因為有執行緒重用的現象
iOS8.0之後的許可權
名稱 | 描述 |
---|---|
QOS_CLASS_USER_INTERACTIVE | 與使用者互動的任務,這些任務通常跟UI級別的重新整理相關,比如動畫,這些任務需要在一瞬間完成 |
QOS_CLASS_USER_INITIATED | 由使用者發起的並且需要立即得到結果的任務,比如滑動scroll view時去載入資料用於後續cell的顯示,這些任務通常跟後續的使用者互動相關,在幾秒或者更短的時間內完成 |
QOS_CLASS_DEFAULT | 優先順序介於user-initiated 和 utility,當沒有 QoS資訊時預設使用,開發者不應該使用這個值來設定自己的任務 |
QOS_CLASS_UTILITY | 一些可能需要花點時間的任務,這些任務不需要馬上返回結果,比如下載的任務,這些任務可能花費幾秒或者幾分鐘的時間 |
QOS_CLASS_BACKGROUND | 這些任務對使用者不可見,比如後臺進行備份的操作,這些任務可能需要較長的時間,幾分鐘甚至幾個小時 |
QOS_CLASS_UNSPECIFIED | 未指定 |
Dispatch Group
dispatch_group是GCD的一項特性,能夠把任務分組。呼叫者可以等待這組任務執行完畢,也可以提供回撥函式之後繼續往下執行,這組任務完成時,呼叫者會得到通知。常用場景比如說,下載一個大的檔案,分塊下載,全部下載完成後再合成一個檔案。再比如同時下載多個圖片,監聽全部下載完後的動作
- 建立group
dispatch_group_t group = dispatch_group_create();
-
新增任務
-
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
將一個任務新增到指定group中
-
dispatch_group_enter(dispatch_group_t group); dispatch_group_leave(dispatch_group_t group);
這兩個函式同上邊一樣的效果,不過一定要注意這兩個函式必須成對出現!否則這一組任務就永遠執行不完。
-
-
監聽任務完成
- dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); 開發者可以傳入block,等dispatch group 執行完畢之後,塊會在特定的執行緒上執行,而不阻塞執行緒。
- long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
timeout參數列示函式在等待dispatch group執行完畢後,應該阻塞多久。如果執行dispatch group所需的時間小於timeout,則返回0,否則返回非0值.此引數可以取常量DISPATCH_TIME_FOREVER,這表示函式會一直等著dispatch group 執行完,而不會超時。此方法會阻塞執行緒。
*** 使用dispatch_group_notify***
//這個例子演示將將一個很大的字串刪除指定幾個字串。比如“abcdefghicladedgdsfs”刪除"ace"三個字串
int const COUNT = 6;
@interface ViewController ()
{
NSMutableArray *values;
dispatch_group_t group;
dispatch_queue_t queue;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
values = [NSMutableArray array];
group = dispatch_group_create();
queue = dispatch_queue_create("com.aa.test", DISPATCH_QUEUE_CONCURRENT);
[self removeChars:@"abcdefghicladedgdsfs" target:@"ace"];
dispatch_group_notify(group, queue, ^{
NSString *str = [values componentsJoinedByString:@""];
NSLog(@"%@",str);
});
}
- (void) removeChars:(NSString *)pBuffer target:(NSString *)target {
for (int i = 0; i<COUNT; i++) {
if (i==COUNT-1) {
NSString *str = [pBuffer substringWithRange:NSMakeRange(pBuffer.length/(COUNT-1)*i,pBuffer.length-pBuffer.length/(COUNT-1)*i)];
[values addObject:str];
}else{
NSString *str = [pBuffer substringWithRange:NSMakeRange(pBuffer.length/(COUNT-1)*i, pBuffer.length/(COUNT-1))];
[values addObject:str];
}
}
for (int i = 0; i<values.count; i++) {
[self handlerString:values[i] target:target index:i];
}
}
- (void)handlerString:(NSString *)string target:(NSString *)target index:(int)index{
dispatch_group_async(group, queue, ^{
values[index] = [self replace:string target:target];
});
}
- (NSString *)replace:(NSString *)string target:(NSString *)target{
for (int i = 0; i<target.length; i++) {
char temp = [target characterAtIndex:i];
string = [string stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%c", temp] withString:@""];
}
return string;
}
@end
複製程式碼
使用dispatch_group_enter和dispatch_group_leave
dispatch_group_t group =dispatch_group_create();
dispatch_queue_t globalQueue=dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
//模擬多執行緒耗時操作
dispatch_group_async(group, globalQueue, ^{
sleep(3);
NSLog(@"%@---block1結束。。。",[NSThread currentThread]);
dispatch_group_leave(group);
});
NSLog(@"%@---1結束。。。",[NSThread currentThread]);
dispatch_group_enter(group);
//模擬多執行緒耗時操作
dispatch_group_async(group, globalQueue, ^{
sleep(3);
NSLog(@"%@---block2結束。。。",[NSThread currentThread]);
dispatch_group_leave(group);
});
NSLog(@"%@---2結束。。。",[NSThread currentThread]);
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@---全部結束。。。",[NSThread currentThread]);
});
複製程式碼
dispatch_async
和dispatch_sync
- dispatch_async
dispatch_async
將指定的Block非同步
的追加到指定的Dispatch Queue中。dispatch_async
函式不會做任何等待
- dispatch_sync
dispatch_sync
將指定的Block同步
的追加到指定的Dispatch Queue。此時dispatch_sync
會一直等待Block執行結束之後,才會返回。執行緒才能接著繼續執行其他程式碼。
如dispatch_group_wait函式一樣,"等待"意味著當前執行緒停止。
例:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"測試2");
});
複製程式碼
執行以上程式碼則會直接崩潰。主要是因為dispatch_sync
在追加Block的過程中同時在等待,等待意味著當前主執行緒已經停止,所以主執行緒無法執行追到到Main Dispatch Queue的Block。
Serial Dispatch Queue也會引起同樣的問題
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"測試");
});
});
複製程式碼
一樣的道理,序列佇列中只會生成一條執行緒,而dispatch_sync
函式使該執行緒處於等待
狀態即停止狀態,所以無法將Block追加到com.test.queue
這個佇列中。
總結:
-
dispatch_sync
函式- 當前queue是序列佇列。 當前queue上呼叫sync函式,並且sync函式中指定的queue也是當前queue。需要執行的block被放到當前queue的隊尾等待執行,因為這是一個序列的queue,呼叫sync函式會阻塞當前佇列,等待block執行 這個block永遠沒有機會執行sync函式不返回,所以當前佇列就永遠被阻塞了,這就造成了死鎖。(這就是問題中在主執行緒呼叫sync函式,並且在sync函式中傳入main_queue作為queue造成死鎖的情況)
- 當前queue是並行佇列。 在並行的queue上面呼叫sync函式,同時傳入當前queue作為引數,並不會造成死鎖,因為block會馬上被執行,所以sync函式也不會一直等待不返回造成死鎖。(並且Block是在當前執行緒上執行。例如如果是在主執行緒上呼叫了
dispatch_sync
,則Block是在主執行緒上執行的)
-
dispatch_async
代表非同步任務,意思不是一定會生成一條執行緒。如果在MainQueue中執行,則不會生成執行緒;如果在Global Queue中有可能會生成。因為執行緒有一個執行緒池,會重用已經完成任務了的執行緒。
柵欄(dispatch_barrier_sync和dispatch_barrier_async)
作用:與併發佇列結合,可以高效率的避免資料競爭的問題
相同點:dispatch_barrier_sync
和dispatch_barrier_async
函式功能一樣就是在併發佇列中將此程式碼插入的地方上下隔開,如果柵欄一樣,兩部分不影響。只有上邊的併發佇列都執行結束之後,下邊的併發佇列才能夠執行。
不同點:dispatch_barrier_sync
程式碼後邊的任務直到dispatch_barrier_sync
執行完才能被追加到佇列中;dispatch_barrier_async
不用程式碼執行完,後邊的任務也會被追加到佇列中。程式碼上的體現就是dispatch_barrier_sync
後邊的程式碼不會執行,dispatch_barrier_async
後邊的程式碼會執行,但是Block不會被執行。
dispatch_apply
This function submits a block to a dispatch queue for multiple invocations and waits for all iterations of the task block to complete before returning. If the target queue is a concurrent queue returned by dispatch_get_global_queue, the block can be invoked concurrently, and it must therefore be reentrant-safe. Using this function with a concurrent queue can be useful as an efficient parallel for loop.
該函式指定次數將指定的Block追加到指定的Queue中,並等待全部處理執行結束。
dispatch_apply(10, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^(size_t index) {
});
複製程式碼
作用:與併發佇列結合,可以更有效執行併發任務。
dispatch_suspend/dispatch_resume
掛起指定的Dispatch Queue
dispatch_suspend(queue);
複製程式碼
恢復指定的Dispatch Queue
dispatch_resume(queue);
複製程式碼
Dispatch Semaphore
當計數為0時等待,計數為1或大於1時不等待
dispatch_semaphore_create(1);//建立1個訊號
複製程式碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//當計數值大於1時,或者在待機中計數值大於1時,對該計數減1並且返回。
複製程式碼
dispatch_semaphore_signal(semaphore);//對計數值加1
複製程式碼
dispatch_once
單例模式,保證在應用程式中只執行一次指定處理的api。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
複製程式碼
Dispatch I/O
非同步序列讀取檔案
NSString *path = [[NSBundle mainBundle] pathForResource:@"iosintroduce" ofType:@"md"];
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_io_t pipe_chanel = dispatch_io_create_with_path(DISPATCH_IO_STREAM,[path UTF8String], 0, 0,queue , ^(int error) {
});
size_t water = 1024;
dispatch_io_set_low_water(pipe_chanel, water);
dispatch_io_set_high_water(pipe_chanel, water);
NSMutableData *totalData = [[NSMutableData alloc] init];
dispatch_io_read(pipe_chanel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
[totalData appendData:(NSData *)data];
}
}
if (done) {
NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
}
});
複製程式碼
非同步並行讀取檔案
NSString *path = [[NSBundle mainBundle] pathForResource:@"iosintroduce" ofType:@"md"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
off_t currentSize = 0;
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
size_t offset = 1024*1024;
dispatch_group_t group = dispatch_group_create();
NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];
for (; currentSize <= fileSize; currentSize += offset) {
dispatch_group_enter(group);
dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
const void *bytes = NULL;
(void)dispatch_data_create_map(data, (const void **)&bytes, &len);
[totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];
}
}
if (done) {
dispatch_group_leave(group);
}
});
}
dispatch_group_notify(group, queue, ^{
NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
});
複製程式碼
dispatch_io_create_with_path
通過路徑建立一個dispatch_io_t
。與之相似的也好幾個方法,比如dispatch_io_create_with_io
dispatch_io_set_high_water
和dispatch_io_set_low_water
分割檔案大小,分別可以設定一次最少讀取和一次最多讀取多大。dispatch_io_read
讀取檔案。與之對應的是dispatch_io_write
將檔案儲存到指定路徑
如果想提高檔案讀取速度,可以嘗試使用Dispatch I/O。
Dispatch Source
倒數計時功能
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 15ULL*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"wake up");
dispatch_source_cancel(timer);
});
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"canceled");
});
dispatch_resume(timer);
複製程式碼
dispatch_after
延遲執行任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
複製程式碼
最快3秒後執行,最慢3秒+一個runloop迴圈時間
dispatch_set_target_queue
改變生成的Dispatch Queue的執行優先順序
//將myQueue優先順序設定為與globalQueue相同
dispatch_queue_t myQueue = dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_set_target_queue(myQueue, globalQueue)
複製程式碼
第一個引數需要變更優先順序的Dispatch Queue,第二個引數指定要使用的執行優先順序相同優先順序的Queue。
個人部落格
已經同步釋出到FlyOceanFish的部落格