GCD用法
GCD
Dispatch Queue介紹
蘋果官方對(duì)GCD的說明:開發(fā)者要做的只是定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中。
dispatch_async(queue, ^{
//要執(zhí)行的任務(wù)
});
多線程編程定義
1個(gè)CPU執(zhí)行的CPU命令列為一條無分叉的路徑,即為”線程“。目前,基本上一個(gè)CPU核一次只能執(zhí)行一個(gè)CPU命令,那么怎樣才能在多條路徑中執(zhí)行CPU命令呢?
OS X和iOS的核心XNU內(nèi)核在發(fā)生操作系統(tǒng)事件時(shí)會(huì)切換執(zhí)行路徑。執(zhí)行中路徑的狀態(tài),例如CPU的寄存器等信息保存到保存到各自路徑專用的內(nèi)存塊中,從切換目標(biāo)路徑專用的內(nèi)存塊中,復(fù)原CPU寄存器等信息,繼續(xù)執(zhí)行切換路徑的CPU命令列。這被稱為“上下文切換”。
由于使用多線程的程序可以在某個(gè)線程和其他線程之間反復(fù)多次進(jìn)行上下文切換,因此看上去就好像1個(gè)CPU核能夠并列執(zhí)行多個(gè)線程一樣。在多個(gè)CPU核的情況下就真的事提供了多個(gè)CPU核并行執(zhí)行多個(gè)線程的技術(shù)。
這種使用多線程編程的技術(shù)被稱為多“多線程編程”。
=
但是多線程編程實(shí)際上是一種已發(fā)生各種問題的編程技術(shù)。比如多個(gè)線程更新相同的資源會(huì)導(dǎo)致數(shù)據(jù)的不一致(數(shù)據(jù)競(jìng)爭(zhēng))、停止等待事件的線程會(huì)導(dǎo)致多個(gè)線程相互等待(死鎖)、使用太多線程會(huì)消耗大量?jī)?nèi)存等。
=
要回避這些問題有很多辦法,但程序都偏于復(fù)雜。
盡管極易發(fā)生各種問題,也應(yīng)該使用多線程編程。這是因?yàn)槎嗑€程編程可以保證應(yīng)用程序響應(yīng)性能。
1 GCD的API
1.1 dispatch_queue_create
dispatch_queue_create函數(shù)可以生成Dispatch Queue
//生成Serial Dispatch
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.myway.gcd.serial", NULL);
//生成Concurrent Dispatch
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.myway.gcd.Concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create使用注意事項(xiàng)
- 雖然Serial Queue和Concurrent Dispatch Queue受到系統(tǒng)資源的限制,但dispatch_queue_create可生成任意多個(gè)Dispatch Queue。
- 生成多個(gè)Serial Dispatch Queue時(shí),多個(gè)Serial Dispatch Queue將并行執(zhí)行。一旦生成Serial Dispatch Queue并追加處理,系統(tǒng)就對(duì)于一個(gè)Serial Dispatch Queue就生成并使用一個(gè)線程。如果過多使用多線程,就會(huì)消耗大量?jī)?nèi)存,引起大量上下文切換,大幅度降低系統(tǒng)的響應(yīng)性能。
- 只在為了避免多線程編程問題之一 ----- 多個(gè)線程更新相同資源導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)時(shí)使用Serial Dispatch Queue。但是Serial Dispatch Queue的生成數(shù)量應(yīng)當(dāng)僅限所必須的數(shù)量,雖然Serial Dispatch Queue比Concurrent能生成更多的線程,但絕不能激動(dòng)之下生成大量的Serial Queue。當(dāng)想并行執(zhí)行不發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)等問題的處理時(shí),使用Concurrent Dispatch Queue。而且對(duì)于Concurrent Dispatch Queue來說,不管生成多少,由于XNU內(nèi)核只使用有效管理的線程,因此不會(huì)發(fā)生Serial Dispatch Queue的那些問題。
1.2 Main Dispatch Queue 和 Global Dispatch Queue
- Main Dispatch Queue 是串行隊(duì)列。 通過dispatch_get_main_queue()獲取
- Global Dispatch Queue 是并行隊(duì)列。 通過dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)獲取
Global Dispatch Queue有4個(gè)優(yōu)先級(jí),用于Global Dispatch Queue管理的線程,將各自使用Global Dispatch Queue的優(yōu)先級(jí)作為線程的優(yōu)先級(jí)使用。
但是Global Dispatch Queue的線程并不能保證實(shí)時(shí)性,因此執(zhí)行優(yōu)先級(jí)只是大致判斷。
Main Dispatch Queue 和 Global Dispatch Queue執(zhí)行dispatch_retain和dispatch_release并不會(huì)引起任何變化,也不會(huì)引起任何問題。
1.3 dispatch_set_target_queue
dispatch_queue_create生成的隊(duì)列都是默認(rèn)優(yōu)先級(jí)的Global Dispatch Queue,可以用dispatch_set_target_queue變更優(yōu)先級(jí)。Main Dispatch Queue 和 Global Dispatch Queue不能隨意變更優(yōu)先級(jí),會(huì)引起不可預(yù)知的錯(cuò)誤。
dispatch_set_target_queue不僅可以變更優(yōu)先級(jí),還可以設(shè)置Dispatch Queue的執(zhí)行階層。
1.4 dispatch_after
//ull 是C語言的數(shù)值字面量,是顯示使用類型時(shí)使用的字符串(表示“unsigned long long”)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"等待至少3秒后執(zhí)行");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"等待至少3秒后執(zhí)行");
});
注意:dispatch_after不是在指定時(shí)間后執(zhí)行處理,而是指定時(shí)間后追加處理到Dispatch Queue。
這段源代碼是3秒后追加Block到Main Dispatch Queue。
因?yàn)镸ain Dispatch Queue在主線程的RunLoop中執(zhí)行,所以在比如每隔1/60秒執(zhí)行的RunLoop中,Block最快在3秒后執(zhí)行最慢在3秒+1/60秒后執(zhí)行,如果Main Dispatch Queue有大量處理追加或主線程本身延遲,這個(gè)時(shí)間會(huì)更長(zhǎng)。
dispatch_time_t類型的值可以用 dispatch_time 或 dispatch_walltime函數(shù)作成。
//通過dispatch_walltime用來計(jì)算絕對(duì)時(shí)間
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.5 dispatch_barrier_async
dispatch_barrier_async(queue, ^{
});
1.6 Dispatch Group
追加到Dispatch Queue中的多個(gè)處理全部結(jié)束后想執(zhí)行結(jié)束處理,這種情況經(jīng)常出現(xiàn)。
如果是Serial Dispatch Queue只要將想執(zhí)行的處理全部追加到Serial Dispatch Queue中并在最后追加結(jié)束處理,即可實(shí)現(xiàn)。但是使用Concurrent Dispatch Queue時(shí)或同時(shí)使用多個(gè)Dispatch Queue時(shí)代碼就會(huì)變得復(fù)雜,這種情況下就要用到Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (NSInteger index = 0; index<10; index++) {
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
[weakSelf setStringValue:[NSString stringWithFormat:@"blk%ld",(long)index]];
});
}
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"done");
// });
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
if (result == 0) {
//屬于group的全部處理執(zhí)行結(jié)束
NSLog(@"屬于group的全部處理執(zhí)行結(jié)束");
}else{
//屬于group的處理未全部執(zhí)行結(jié)束
NSLog(@"屬于group的處理未全部執(zhí)行結(jié)束");
}
dispatch_group_enter(_dispatchGroup);
dispatch_group_leave(_dispatchGroup);
dispatch_group_notify(_dispatchGroup, dispatch_get_main_queue(), ^{
});
1.7 dispatch_apply
dispatch_apply按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");//done必定在最后的位置輸出,因?yàn)閐ispatch_apply會(huì)等待全部處理執(zhí)行結(jié)束
因?yàn)閐ispatch_apply會(huì)等待所有處理結(jié)束,所以推薦在dispatch_async中非同步執(zhí)行dispatch_apply
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");//done必定在最后的位置輸出,因?yàn)閐ispatch_apply會(huì)等待全部處理執(zhí)行結(jié)束
dispatch_async(dispatch_get_main_queue(), ^{
//main queue 處理UI等;
});
});
1.8 dispatch_suspend/dispatch_resume
```
dispatch_suspend(queue);
dispatch_resume(queue);
```
1.9 dispatch_semaphore_t
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
GCD總結(jié)
線程是魔鬼,應(yīng)謹(jǐn)慎使用多線程,合理使用多線程可以保證程序運(yùn)行流暢。如果不合理多線程,可能會(huì)引起很多問題,如:大量使用多線程可能會(huì)很快用盡線程棧內(nèi)存。
Dispatch Queues有以下優(yōu)點(diǎn): 他們提供直接而簡(jiǎn)單的編程接口
- 他們提供自動(dòng)和完全的線程池管理
- 他們提供協(xié)作組件的速度。
- 他們更加內(nèi)存高效(因?yàn)榫€程棧不在應(yīng)用的內(nèi)存中,而在內(nèi)核的內(nèi)存中?)
- 他們不會(huì)導(dǎo)致內(nèi)核負(fù)載過高
- 他們使用dispatch queue異步處理任務(wù),不會(huì)讓queue死鎖
- 他們?cè)诟?jìng)爭(zhēng)中保持平和。
- 串行dispatch queue提供了一個(gè)比鎖和其他同步機(jī)制更加高效的選擇
你提交給dispatch queue的任務(wù)必須封裝在一個(gè)函數(shù)或者一個(gè)block 對(duì)象中。block對(duì)象是在ios4中的類似于函數(shù)指針但有有額外優(yōu)點(diǎn)的c語言特性。與其定義blocks在他們自己的詞法作用域,你一般定義blocks在他們能訪問的一個(gè)函數(shù)或者方法內(nèi)部。blocks可以移出他們?cè)嫉姆秶缓髲?fù)制到堆上,這就是當(dāng)你將他們提交到dispatch queue上發(fā)生的。所有這些語義使得使得少量代碼就可以實(shí)現(xiàn)非常動(dòng)態(tài)的任務(wù)成為可能。
Dispatch queues是GCD技術(shù)的一部分和C運(yùn)行時(shí)的一部分。
Dispatch Sources
Dispatch Sources是基于C的機(jī)制處理指定類型的系統(tǒng)異步事件。一個(gè)dispatch source封裝包括特定類型的系統(tǒng)事件的信息然后提交給一個(gè)指定的block對(duì)象或者函數(shù)給dispatch queue當(dāng)事件發(fā)生時(shí)。你可以使用dispatch sources來監(jiān)視以下系統(tǒng)事件
定時(shí)器(Timers)
信號(hào)處理(Signal handlers)
描述符相關(guān)的事件(Descriptor-related events)
進(jìn)程相關(guān)事件(Process-related events)
端口事件(Mach port events)
你觸發(fā)的用戶事件(Custom events that you trigger)
Dispache sources是GCD技術(shù)的一部分。