什么是GCD
Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進行并發(fā)程序編寫。他們都允許程序?qū)⑷蝿涨蟹譃槎鄠€單一任務然后提交至工作隊列來并發(fā)地或者串行地執(zhí)行。GCD比之NSOpertionQueue更底層更高效,并且它不是Cocoa框架的一部分。
除了代碼的平行執(zhí)行能力,GCD還提供高度集成的事件控制系統(tǒng)??梢栽O(shè)置句柄來響應文件描述符、mach ports(Mach port 用于 OS X上的進程間通訊)、進程、計時器、信號、用戶生成事件。這些句柄通過GCD來并發(fā)執(zhí)行。
GCD的API很大程度上基于block,當然,GCD也可以脫離block來使用,比如使用傳統(tǒng)c機制提供函數(shù)指針和上下文指針。實踐證明,當配合block使用時,GCD非常簡單易用且能發(fā)揮其最大能力。
理解串行、并發(fā)及同步異步
串行和并發(fā)
串行和并發(fā)描述了任務之間執(zhí)行的時機。任務如果是串行的,那么在同一時間只執(zhí)行一個任務。并發(fā)的多個任務被執(zhí)行的時候,可能是在同一時間。
同步和異步
同步和異步描述了一個函數(shù)相對于另一個函數(shù)何時執(zhí)行完畢。同步的函數(shù)只有當它調(diào)用的任務執(zhí)行完,才會返回。而異步函數(shù),會立即返回。雖然它也命令任務執(zhí)行完,但它并不等待任務執(zhí)行完。如此,異步函數(shù)就不會阻塞當前線程。(這樣說可能有些過于抽象了,個人理解的是,在同步的時候沒有開啟子線程的能力,而在異步的時候具備開啟子線程的能力)。
隊列
串行隊列
1)使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create(“隊列名”,NULL);
dispatch_queue_t queue = dispatch_queue_create(“隊列名”,DISPATCH_QUEUE_SERIAL);
兩者等效.
2)使用主隊列(在主隊列中的任務,都會放到主線程中執(zhí)行,它也是唯一一個允許更新UI的隊列,所以要是開啟子線程的時候要更新UI的情況下一定要用主隊列進行更新)
dispatch_queue_t queue = dispatch_get_main_queue();
并發(fā)隊列(GCD默認已經(jīng)提供了全局的并發(fā)隊列)
1)獲得全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority,unsigned long flags);
前一個參數(shù)是優(yōu)先級,有以下幾種:
DISPATCH_QUEUE_PRIORITY_HIGH 2//高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0//默認
DISPATCH_QUEUE_PRIORITY_LOW (-2)//低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MN//后臺
2)自定義并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create(“隊列名”,DISPATCH_QUEUE_CONCURRENT);
在開發(fā)中基本上就是通過dispatch_async和dispatch_sync 分別配合著上面的三種隊列使用,但是更多的情況下基本上就是dispatch_async和全局隊列配合使用,并用主線程中更新UI。
dispatch_async
1)全局隊列
//在這里獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:08:51.114 GCD[81282:8816505] task2<NSThread: 0x7fb36b900080>{number = 2, name = (null)}
2016-07-31 21:08:51.114 GCD[81282:8816516] task3<NSThread: 0x7fb369ff59a0>{number = 3, name = (null)}
2016-07-31 21:08:51.114 GCD[81282:8816512] task1<NSThread: 0x7fb369e2bba0>{number = 4, name = (null)}
可以看出GCD開啟了子線程并且并發(fā)執(zhí)行
2)主隊列
//在這里獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:14:37.452 GCD[81293:8820034] task1<NSThread: 0x7ffb7a000720>{number = 1, name = main}
2016-07-31 21:14:37.452 GCD[81293:8820034] task2<NSThread: 0x7ffb7a000720>{number = 1, name = main}
2016-07-31 21:14:37.452 GCD[81293:8820034] task3<NSThread: 0x7ffb7a000720>{number = 1, name = main}
可以看出來雖然用了dispatch_async但是并沒有開啟子線程來執(zhí)行任務,這是因為隊列的性質(zhì)決定的,所以在用主隊列的時候要注意。
dispatch_sync
1)全局隊列
//在這里獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 21:19:06.993 GCD[81311:8822954] task1<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
2016-07-31 21:19:06.994 GCD[81311:8822954] task2<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
2016-07-31 21:19:06.994 GCD[81311:8822954] task3<NSThread: 0x7f9208e04fe0>{number = 1, name = main}
可以看出來dispatch_sync中雖然用的是全局隊列,但是并沒有并發(fā),因為dispatch_sync沒有開啟子線程的能力,只能在主線程中串行執(zhí)行任務。
2)主隊列
//在這里獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
這樣是不會有打印結(jié)果的,因為發(fā)生了死鎖,在這個過程中,主線等著GCD運行完成后繼續(xù)運行而GCD等待著主線運行完成后繼續(xù)運行,產(chǎn)生了死鎖,所以不能這樣使用。
dispatch_after
//在這里只有用主隊列有意義
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
NSLog(@"主線");
打印結(jié)果
2016-07-31 21:37:14.292 GCD[81346:8829855] 主線
2016-07-31 21:37:17.292 GCD[81346:8829855] task1<NSThread: 0x7fee08604ea0>{number = 1, name = main}
可以看出在block中的任務被延遲了3秒鐘執(zhí)行。但是這個是有問題的,這里只是延時提交了block,并不是延時后立即執(zhí)行。所以dispatch_after不能精準的控制運行狀態(tài)。
dispatch_once
dispatch_once是一種線程安全的方式,執(zhí)行只執(zhí)行一次代碼塊保證多線程安全。
+ (instancetype)sharedManager
{
static ToolManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[ToolManager alloc] init];
});
return sharedPhotoManager;
}
dispatch_group
有的時候我們要在完成一些任務的前提下再去完成后面的任務,這個時候就要用到dispatch_group。
1)同步的方式(dispatch_group_wait)(這個方法會阻塞線程,后面的代碼要等到wait完成后運行)
//由于我們用了同步的方式dispatch_group_wait,它會阻塞當前線程,所以我們在整個方法外面套上了dispatch_async,使它在后臺執(zhí)行而不會阻塞主線程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
//dispatch_group_enter告知group,一個任務開始了。我們需要將dispatch_group_enter與dispatch_group_leave配對,否則我們會遇到莫名其妙的崩潰。
dispatch_group_enter(downloadGroup);
sleep(2);
NSLog(@"task%ld",i);
//dispatch_group_leave告知group,此任務執(zhí)行完畢,要注意與dispatch_group_enter配對。
dispatch_group_leave(downloadGroup);
}
//dispatch_group_wait等待所有任務完成或者超時,在此處我們設(shè)置等待時間為永遠DISPATCH_TIME_FOREVER
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
//當以上所有任務執(zhí)行完后,我們在主隊列調(diào)用任務完成的block。
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線");
});
});
在這里有個問題,就是說如果我們將其中的超時時間設(shè)置成5秒,但是其中的一個任務要延時10秒,那么這個任務不會因為這個超時時間到了停止,還會繼續(xù)完成任務,但是不會等到這個任務完成后再去執(zhí)行dispatch_group_wait后面的代碼。
2)異步的方式(dispatch_group_notify)(不會阻塞線程,后面的代碼會執(zhí)行,不用等待這些任務和notify)
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task1%@",[NSThread currentThread]);
});
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task2%@",[NSThread currentThread]);
});
dispatch_group_async(downloadGroup, dispatch_get_global_queue(0, 0), ^{
NSLog(@"task3%@",[NSThread currentThread]);
});
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"主線");
});
其中還涉及到了dispatch_group_enter和dispatch_group_leave,我理解這個就是將任務添加到隊列中,并且他們要成對的出現(xiàn)。他們將任務添加到當前的線程中,也就是說如果任務都沒有開啟子線程,那么添加進去的就是在當前的線程中串行執(zhí)行,如果任務開啟了子線程,那么添加進去的就是在相對的子線程可能串行,可能并行(這個要看隊列的性質(zhì))
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
});
這兩種的性質(zhì)是一樣的。
dispatch_apply
dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t i) {
sleep(2);
NSLog(@"任務組%d完成%@",i,[NSThread currentThread]);
});
打印結(jié)果
2016-07-31 23:54:15.378 GCD[81633:8885238] 任務組0完成<NSThread: 0x7fbaf3704c10>{number = 1, name = main}
2016-07-31 23:54:15.381 GCD[81633:8885283] 任務組2完成<NSThread: 0x7fbaf3623960>{number = 2, name = (null)}
2016-07-31 23:54:15.381 GCD[81633:8885275] 任務組1完成<NSThread: 0x7fbaf3526f50>{number = 3, name = (null)}
dispatch_apply和for循環(huán)都是串行的,當dispatch_apply中的工作全都完成后才會返回,可以看出來里面的是并發(fā)執(zhí)行的所以返回的先后順序是不能確定的,不能和for循環(huán)一樣用。
dispatch_suspend和dispatch_resume
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一個block,延時5秒打印。
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds...");
});
//提交第二個block,也是延時5秒打印
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds again...");
});
//延時一秒
NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];
//掛起隊列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延時10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];
//恢復隊列
dispatch_resume(queue);
NSLog(@"resume...");
打印結(jié)果
2016-08-01 13:46:29.817 GCD[81925:8937362] sleep 1 second...
2016-08-01 13:46:30.818 GCD[81925:8937362] suspend...
2016-08-01 13:46:30.819 GCD[81925:8937362] sleep 10 second...
2016-08-01 13:46:34.819 GCD[81925:8937412] After 5 seconds...
2016-08-01 13:46:40.819 GCD[81925:8937362] resume...
2016-08-01 13:46:45.820 GCD[81925:8937412] After 5 seconds again...
這兩個就是將任務掛起和恢復的功能,從結(jié)果可以看出來當隊列掛起后第一個block還是在運行,并且能夠正常的輸出??芍?,dispatch_suspend并不會停止正在運行的block,只會暫停后續(xù)的block的執(zhí)行。
dispatch_barrier_async和dispatch_barrier_sync
dispatch_queue_t queue = dispatch_queue_create("msdf", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"task1");
});
dispatch_async(queue, ^{
sleep(5);
NSLog(@"task2");
});
dispatch_barrier_async(queue, ^{
sleep(3);
NSLog(@"barrier");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task3");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task4");
});
dispatch_barrier_async的作用就是向某個隊列插入一個block,當目前正在執(zhí)行的block運行完成后,阻塞這個block后面添加的block,只運行這個block直到完成,然后再繼續(xù)后續(xù)的任務。
dispatch_barrier\(a)sync只在自己創(chuàng)建的并發(fā)隊列上有效,在全局(Global)并發(fā)隊列、串行隊列上,效果跟dispatch_(a)sync效果一樣。
接下來就來看看dispatch_barrier_sync和dispatch_barrier_async的相同點與不同點
相同點:兩者只有前面的任務執(zhí)行完成之后并且執(zhí)行完成barrier中的代碼后才能執(zhí)行后面的任務。
兩者都能夠阻塞當前的線程
不同點:dispatch_barrier_async在阻塞當前線程時,不需要等待barrier中的代碼完成后再運行后面的非任務代碼;
dispatch_barrier_sync在阻塞當前線程時,需要等待barrier中的代碼完成后再運行后面的非任務代碼。
dispatch_semaphore
信號量是一個整形值并且具有一個初始計數(shù)值,并且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數(shù)會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數(shù)器大于零,然后線程會減少這個計數(shù)。
在GCD中有三個函數(shù)是semaphore的操作,分別是:
dispatch_semaphore_create 創(chuàng)建一個semaphore
dispatch_semaphore_signal 發(fā)送一個信號
dispatch_semaphore_wait 等待信號
簡單的介紹一下這三個函數(shù),第一個函數(shù)有一個整形的參數(shù),我們可以理解為信號的總量,dispatch_semaphore_signal是發(fā)送一個信號,自然會讓信號總量加1,dispatch_semaphore_wait等待信號,當信號總量少于0的時候就會一直等待,否則就可以正常的執(zhí)行,并讓信號總量-1,根據(jù)這樣的原理,我們便可以快速的創(chuàng)建一個并發(fā)控制來同步任務和有限資源訪問控制。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
簡單的介紹一下這一段代碼,創(chuàng)建了一個初使值為10的semaphore,每一次for循環(huán)都會創(chuàng)建一個新的線程,線程結(jié)束的時候會發(fā)送一個信號,線程創(chuàng)建之前會信號等待,所以當同時創(chuàng)建了10個線程之后,for循環(huán)就會阻塞,等待有線程結(jié)束之后會增加一個信號才繼續(xù)執(zhí)行,如此就形成了對并發(fā)的控制,如上就是一個并發(fā)數(shù)為10的一個線程隊列。
dispatch_get_current_queue
現(xiàn)在這個方法已經(jīng)被不建議使用了,但是就像在此強調(diào)一下,這個方法用不好會造成死鎖,在此不再贅余。