一、簡介
在iOS所 有實(shí)現(xiàn)多線程的方案中,GCD應(yīng)該是最有魅力的,因?yàn)镚CD本身是蘋果公司為多核的并行運(yùn)算提出的解決方案。GCD在工作時(shí)會(huì)自動(dòng)利用更多的處理器核心, 以充分利用更強(qiáng)大的機(jī)器。GCD是Grand Central Dispatch的簡稱,它是基于C語言的。如果使用GCD,完全由系統(tǒng)管理線程,我們不需要編寫線程代碼。只需定義想要執(zhí)行的任務(wù),然后添加到適當(dāng)?shù)恼{(diào)度隊(duì)列(dispatch queue)。GCD會(huì)負(fù)責(zé)創(chuàng)建線程和調(diào)度你的任務(wù),系統(tǒng)直接提供線程管理
二、調(diào)度隊(duì)列(dispath queue)
1.GCD的一個(gè)重要概念是隊(duì)列,它的核心理念:將長期運(yùn)行的任務(wù)拆分成多個(gè)工作單元,并將這些單元添加到dispath queue中,系統(tǒng)會(huì)為我們管理這些dispath queue,為我們在多個(gè)線程上執(zhí)行工作單元,我們不需要直接啟動(dòng)和管理后臺線程。
2.系統(tǒng)提供了許多預(yù)定義的dispath queue,包括可以保證始終在主線程上執(zhí)行工作的dispath queue。也可以創(chuàng)建自己的dispath queue,而且可以創(chuàng)建任意多個(gè)。GCD的dispath queue嚴(yán)格遵循FIFO(先進(jìn)先出)原則,添加到dispath queue的工作單元將始終按照加入dispath queue的順序啟動(dòng)。
3.dispatch queue按先進(jìn)先出的順序,串行或并發(fā)地執(zhí)行任務(wù)
1> serial dispatch queue一次只能執(zhí)行一個(gè)任務(wù), 當(dāng)前任務(wù)完成才開始出列并啟動(dòng)下一個(gè)任務(wù)
2> concurrent dispatch queue則盡可能多地啟動(dòng)任務(wù)并發(fā)執(zhí)行
三、創(chuàng)建和管理dispatch queue
1.獲得全局并發(fā)Dispatch Queue (concurrent dispatch queue)
1> 并發(fā)dispatch queue可以同時(shí)并行地執(zhí)行多個(gè)任務(wù),不過并發(fā)queue仍然按先進(jìn)先出的順序來啟動(dòng)任務(wù)。并發(fā)queue會(huì)在之前的任務(wù)完成之前就出列下一個(gè)任務(wù)并開 始執(zhí)行。并發(fā)queue同時(shí)執(zhí)行的任務(wù)數(shù)量會(huì)根據(jù)應(yīng)用和系統(tǒng)動(dòng)態(tài)變化,各種因素包括:可用核數(shù)量、其它進(jìn)程正在執(zhí)行的工作數(shù)量、其它串行dispatch queue中優(yōu)先任務(wù)的數(shù)量等.
2> 系統(tǒng)給每個(gè)應(yīng)用提供三個(gè)并發(fā)dispatch queue,整個(gè)應(yīng)用內(nèi)全局共享,三個(gè)queue的區(qū)別是優(yōu)先級。你不需要顯式地創(chuàng)建這些queue,使用dispatch_get_global_queue函數(shù)來獲取這三個(gè)queue:
//?獲取默認(rèn)優(yōu)先級的全局并發(fā)dispatch?queue
dispatch_queue_t??queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
第一個(gè)參數(shù)用于指定優(yōu)先級,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個(gè)常量來獲取高和低優(yōu)先級的兩個(gè)queue;第二個(gè)參數(shù)目前未使用到,默認(rèn)0即可
3> 雖然dispatch queue是引用計(jì)數(shù)的對象,但你不需要retain和release全局并發(fā)queue。因?yàn)檫@些queue對應(yīng)用是全局的,retain和 release調(diào)用會(huì)被忽略。你也不需要存儲(chǔ)這三個(gè)queue的引用,每次都直接調(diào)用dispatch_get_global_queue獲得queue 就行了。
2.創(chuàng)建串行Dispatch Queue (serial dispatch queue)
1> 應(yīng)用的任務(wù)需要按特定順序執(zhí)行時(shí),就需要使用串行Dispatch Queue,串行queue每次只能執(zhí)行一個(gè)任務(wù)。你可以使用串行queue來替代鎖,保護(hù)共享資源 或可變的數(shù)據(jù)結(jié)構(gòu)。和鎖不一樣的是,串行queue確保任務(wù)按可預(yù)測的順序執(zhí)行。而且只要你異步地提交任務(wù)到串行queue,就永遠(yuǎn)不會(huì)產(chǎn)生死鎖
2> 你必須顯式地創(chuàng)建和管理所有你使用的串行queue,應(yīng)用可以創(chuàng)建任意數(shù)量的串行queue,但不要為了同時(shí)執(zhí)行更多任務(wù)而創(chuàng)建更多的串行queue。如果你需要并發(fā)地執(zhí)行大量任務(wù),應(yīng)該把任務(wù)提交到全局并發(fā)queue
3> 利用dispatch_queue_create函數(shù)創(chuàng)建串行queue,兩個(gè)參數(shù)分別是queue名和一組queue屬性
dispatch_queue_t?queue;
queue?=?dispatch_queue_create("cn.itcast.queue",?NULL);
3.運(yùn)行時(shí)獲得公共Queue
GCD提供了函數(shù)讓應(yīng)用訪問幾個(gè)公共dispatch queue:
1> 使用dispatch_get_current_queue函數(shù)作為調(diào)試用途,或者測試當(dāng)前queue的標(biāo)識。在block對象中調(diào)用這個(gè)函數(shù)會(huì)返回 block提交到的queue(這個(gè)時(shí)候queue應(yīng)該正在執(zhí)行中)。在block對象之外調(diào)用這個(gè)函數(shù)會(huì)返回應(yīng)用的默認(rèn)并發(fā)queue。
2> 使用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程關(guān)聯(lián)的串行dispatch queue
3> 使用dispatch_get_global_queue來獲得共享的并發(fā)queue
4.Dispatch Queue的內(nèi)存管理
1> Dispatch Queue和其它dispatch對象(還有dispatch source)都是引用計(jì)數(shù)的數(shù)據(jù)類型。當(dāng)你創(chuàng)建一個(gè)串行dispatch queue時(shí),初始引用計(jì)數(shù)為 1,你可以使用dispatch_retain和dispatch_release函數(shù)來增加和減少引用計(jì)數(shù)。當(dāng)引用計(jì)數(shù)到達(dá) 0 時(shí),系統(tǒng)會(huì)異步地銷毀這個(gè)queue
2> 對dispatch對象(如dispatch queue)retain和release 是很重要的,確保它們被使用時(shí)能夠保留在內(nèi)存中。和OC對象一樣,通用的規(guī)則是如果使用一個(gè)傳遞過來的queue,你應(yīng)該在使用前retain,使用完之 后release
3> 你不需要retain或release全局dispatch queue,包括全局并發(fā)dispatch queue和main dispatch queue
4> 即使你實(shí)現(xiàn)的是自動(dòng)垃圾收集的應(yīng)用,也需要retain和release創(chuàng)建的dispatch queue和其它dispatch對象。GCD 不支持垃圾收集模型來回收內(nèi)存
四、添加任務(wù)到queue
要執(zhí)行一個(gè)任務(wù),你需要將它添加到一個(gè)適當(dāng)?shù)膁ispatch queue,你可以單個(gè)或按組來添加,也可以同步或異步地執(zhí)行一個(gè)任務(wù),也。一旦進(jìn)入到queue,queue會(huì)負(fù)責(zé)盡快地執(zhí)行你的任務(wù)。一般可以用一個(gè)block來封裝任務(wù)內(nèi)容。
1.添加單個(gè)任務(wù)到queue
1> 異步添加任務(wù)
你可以異步或同步地添加一個(gè)任務(wù)到Queue,盡可能地使用 dispatch_async或dispatch_async_f函數(shù)異步地調(diào)度任務(wù)。因?yàn)樘砑尤蝿?wù)到Queue中時(shí),無法確定這些代碼什么時(shí)候能夠執(zhí) 行。因此異步地添加block或函數(shù),可以讓你立即調(diào)度這些代碼的執(zhí)行,然后調(diào)用線程可以繼續(xù)去做其它事情。特別是應(yīng)用主線程一定要異步地 dispatch 任務(wù),這樣才能及時(shí)地響應(yīng)用戶事件
2> 同步添加任務(wù)
少數(shù)時(shí)候你可能希望同步地調(diào)度任務(wù),以避免競爭條件或其它同步錯(cuò)誤。 使用dispatch_sync和dispatch_sync_f函數(shù)同步地添加任務(wù)到Queue,這兩個(gè)函數(shù)會(huì)阻塞當(dāng)前調(diào)用線程,直到相應(yīng)任務(wù)完成執(zhí)行。注意:絕對不要在任務(wù)中調(diào)用 dispatch_sync或dispatch_sync_f函數(shù),并同步調(diào)度新任務(wù)到當(dāng)前正在執(zhí)行的 queue。對于串行queue這一點(diǎn)特別重要,因?yàn)檫@樣做肯定會(huì)導(dǎo)致死鎖;而并發(fā)queue也應(yīng)該避免這樣做。
3> 代碼演示
[java]view plaincopy
//?調(diào)用前,查看下當(dāng)前線程
NSLog(@"當(dāng)前調(diào)用線程:%@",?[NSThread?currentThread]);
//?創(chuàng)建一個(gè)串行queue
dispatch_queue_t?queue?=?dispatch_queue_create("cn.itcast.queue",?NULL);
dispatch_async(queue,?^{
NSLog(@"開啟了一個(gè)異步任務(wù),當(dāng)前線程:%@",?[NSThread?currentThread]);
});
dispatch_sync(queue,?^{
NSLog(@"開啟了一個(gè)同步任務(wù),當(dāng)前線程:%@",?[NSThread?currentThread]);
});
//?銷毀隊(duì)列
dispatch_release(queue);
打印信息:
2013-02-03?09:03:37.348?thread[6491:c07]?當(dāng)前調(diào)用線程:{name?=?(null),?num?=?1}
2013-02-03?09:03:37.349?thread[6491:1e03]?開啟了一個(gè)異步任務(wù),當(dāng)前線程:{name?=?(null),?num?=?3}
2013-02-03?09:03:37.350?thread[6491:c07]?開啟了一個(gè)同步任務(wù),當(dāng)前線程:{name?=?(null),?num?=?1}
2.并發(fā)地執(zhí)行循環(huán)迭代
如果你使用循環(huán)執(zhí)行固定次數(shù)的迭代, 并發(fā)dispatch queue可能會(huì)提高性能。
例如下面的for循環(huán):
int?i;
int?count?=?10;
for?(i?=?0;?i?<?count;?i++)?{
printf("%d??",i);
}
1> 如果每次迭代執(zhí)行的任務(wù)與其它迭代獨(dú)立無關(guān),而且循環(huán)迭代執(zhí)行順序也無關(guān)緊要的話,你可以調(diào)用dispatch_apply或 dispatch_apply_f函數(shù)來替換循環(huán)。這兩個(gè)函數(shù)為每次循環(huán)迭代將指定的block或函數(shù)提交到queue。當(dāng)dispatch到并發(fā) queue時(shí),就有可能同時(shí)執(zhí)行多個(gè)循環(huán)迭代。用dispatch_apply或dispatch_apply_f時(shí)你可以指定串行或并發(fā) queue。并發(fā)queue允許同時(shí)執(zhí)行多個(gè)循環(huán)迭代,而串行queue就沒太大必要使用了。
下面代碼使用dispatch_apply替換了for循環(huán),你傳遞的block必須包含一個(gè)size_t類型的參數(shù),用來標(biāo)識當(dāng)前循環(huán)迭代。第一次迭代這個(gè)參數(shù)值為0,最后一次值為count - 1
//?獲得全局并發(fā)queue
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
size_t?count?=?10;
dispatch_apply(count,?queue,?^(size_t?i)?{
printf("%zd?",?i);
});
//?銷毀隊(duì)列
dispatch_release(queue);
打印信息:
1?2?0?3?4?5?6?7?8?9
可以看出,這些迭代是并發(fā)執(zhí)行的
和普通for循環(huán)一樣,dispatch_apply和 dispatch_apply_f函數(shù)也是在所有迭代完成之后才會(huì)返回,因此這兩個(gè)函數(shù)會(huì)阻塞當(dāng)前線程,主線程中調(diào)用這兩個(gè)函數(shù)必須小心,可能會(huì)阻止事件 處理循環(huán)并無法響應(yīng)用戶事件。所以如果循環(huán)代碼需要一定的時(shí)間執(zhí)行,可以考慮在另一個(gè)線程中調(diào)用這兩個(gè)函數(shù)。如果你傳遞的參數(shù)是串行queue,而且正是 執(zhí)行當(dāng)前代碼的queue,就會(huì)產(chǎn)生死鎖。
3.在主線程中執(zhí)行任務(wù)
1> GCD提供一個(gè)特殊的dispatch queue,可以在應(yīng)用的主線程中執(zhí)行任務(wù)。只要應(yīng)用主線程設(shè)置了run loop(由CFRunLoopRef類型或NSRunLoop對象管理),就會(huì)自動(dòng)創(chuàng)建這個(gè)queue,并且最后會(huì)自動(dòng)銷毀。非Cocoa應(yīng)用如果不顯 式地設(shè)置run loop, 就必須顯式地調(diào)用dispatch_main函數(shù)來顯式地激活這個(gè)dispatch queue,否則雖然你可以添加任務(wù)到queue,但任務(wù)永遠(yuǎn)不會(huì)被執(zhí)行。
2> 調(diào)用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程的dispatch queue,添加到這個(gè)queue的任務(wù)由主線程串行化執(zhí)行
3> 代碼實(shí)現(xiàn),比如異步下載圖片后,回到主線程顯示圖片
//?異步下載圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
NSURL?*url?=?[NSURL?URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
UIImage?*image?=?[UIImage?imageWithData:[NSData?dataWithContentsOfURL:url]];
//?回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(),?^{
self.imageView.image?=?image;
});
});
4.任務(wù)中使用Objective-C對象
GCD支持Cocoa內(nèi)存管理機(jī)制,因此可以在提交到queue的 block中自由地使用Objective-C對象。每個(gè)dispatch queue維護(hù)自己的autorelease pool確保釋放autorelease對象,但是queue不保證這些對象實(shí)際釋放的時(shí)間。如果應(yīng)用消耗大量內(nèi)存,并且創(chuàng)建大量autorelease 對象,你需要?jiǎng)?chuàng)建自己的autorelease pool,用來及時(shí)地釋放不再使用的對象。
五、暫停和繼續(xù)queue
我們可以使用dispatch_suspend函數(shù)暫 停一個(gè)queue以阻止它執(zhí)行block對象;使用dispatch_resume函數(shù)繼續(xù)dispatch queue。調(diào)用dispatch_suspend會(huì)增加queue的引用計(jì)數(shù),調(diào)用dispatch_resume則減少queue的引用計(jì)數(shù)。當(dāng)引用 計(jì)數(shù)大于0時(shí),queue就保持掛起狀態(tài)。因此你必須對應(yīng)地調(diào)用suspend和resume函數(shù)。掛起和繼續(xù)是異步的,而且只在執(zhí)行block之間(比 如在執(zhí)行一個(gè)新的block之前或之后)生效。掛起一個(gè)queue不會(huì)導(dǎo)致正在執(zhí)行的block停止。
六、Dispatch Group的使用
假設(shè)有這樣一個(gè)需求:從網(wǎng)絡(luò)上下載兩張不同的圖片,然后顯示到不同的UIImageView上去,一般可以這樣實(shí)現(xiàn)
//?根據(jù)url獲取UIImage
-?(UIImage?*)imageWithURLString:(NSString?*)urlString?{
NSURL?*url?=?[NSURL?URLWithString:urlString];
NSData?*data?=?[NSData?dataWithContentsOfURL:url];
return?[UIImage?imageWithData:data];
}
-?(void)downloadImages?{
//?異步下載圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
//?下載第一張圖片
NSString?*url1?=?@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
UIImage?*image1?=?[self?imageWithURLString:url1];
//?下載第二張圖片
NSString?*url2?=?@"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
UIImage?*image2?=?[self?imageWithURLString:url2];
//?回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(),?^{
self.imageView1.image?=?image1;
self.imageView2.image?=?image2;
});
});
}
雖然這種方案 可以解決問題,但其實(shí)兩張圖片的下載過程并不需要按順序執(zhí)行,并發(fā)執(zhí)行它們可以提高執(zhí)行速度。有個(gè)注意點(diǎn)就是必須等兩張圖片都下載完畢后才能回到主線程顯 示圖片。Dispatch Group能夠在這種情況下幫我們提升性能。下面先看看Dispatch Group的用處:
我們可以使用dispatch_group_async函數(shù)將多個(gè) 任務(wù)關(guān)聯(lián)到一個(gè)Dispatch Group和相應(yīng)的queue中,group會(huì)并發(fā)地同時(shí)執(zhí)行這些任務(wù)。而且Dispatch Group可以用來阻塞一個(gè)線程, 直到group關(guān)聯(lián)的所有的任務(wù)完成執(zhí)行。有時(shí)候你必須等待任務(wù)完成的結(jié)果,然后才能繼續(xù)后面的處理。
下面用Dispatch Group優(yōu)化上面的代碼:
//?根據(jù)url獲取UIImage
-?(UIImage?*)imageWithURLString:(NSString?*)urlString?{
NSURL?*url?=?[NSURL?URLWithString:urlString];
NSData?*data?=?[NSData?dataWithContentsOfURL:url];
//?這里并沒有自動(dòng)釋放UIImage對象
return?[[UIImage?alloc]?initWithData:data];
}
-?(void)downloadImages?{
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
//?異步下載圖片
dispatch_async(queue,?^{
//?創(chuàng)建一個(gè)組
dispatch_group_t?group?=?dispatch_group_create();
__block?UIImage?*image1?=?nil;
__block?UIImage?*image2?=?nil;
//?關(guān)聯(lián)一個(gè)任務(wù)到group
dispatch_group_async(group,?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
//?下載第一張圖片
NSString?*url1?=?@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1?=?[self?imageWithURLString:url1];
});
//?關(guān)聯(lián)一個(gè)任務(wù)到group
dispatch_group_async(group,?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
//?下載第一張圖片
NSString?*url2?=?@"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2?=?[self?imageWithURLString:url2];
});
//?等待組中的任務(wù)執(zhí)行完畢,回到主線程執(zhí)行block回調(diào)
dispatch_group_notify(group,?dispatch_get_main_queue(),?^{
self.imageView1.image?=?image1;
self.imageView2.image?=?image2;
//?千萬不要在異步線程中自動(dòng)釋放UIImage,因?yàn)楫?dāng)異步線程結(jié)束,異步線程的自動(dòng)釋放池也會(huì)被銷毀,那么UIImage也會(huì)被銷毀
//?在這里釋放圖片資源
[image1?release];
[image2?release];
});
//?釋放group
dispatch_release(group);
});
}
dispatch_group_notify函數(shù)用來指定一個(gè)額外的block,該block將在group中所有任務(wù)完成后執(zhí)行