iOS開發(fā)之GCD使用總結(jié)
GCD是iOS的一種底層多線程機制,今天總結(jié)一下GCD的常用API和概念,希望對大家的學(xué)習(xí)起到幫助作用。
GCD隊列的概念
在多線程開發(fā)當(dāng)中,程序員只要將想做的事情定義好,并追加到DispatchQueue(派發(fā)隊列)當(dāng)中就好了。
派發(fā)隊列分為兩種,一種是串行隊列(SerialDispatchQueue),一種是并行隊列(ConcurrentDispatchQueue)。
一個任務(wù)就是一個block,比如,將任務(wù)添加到隊列中的代碼是:
1 dispatch_async(queue, block);
當(dāng)給queue添加多個任務(wù)時,如果queue是串行隊列,則它們按順序一個個執(zhí)行,同時處理的任務(wù)只有一個。
當(dāng)queue是并行隊列時,不論第一個任務(wù)是否結(jié)束,都會立刻開始執(zhí)行后面的任務(wù),也就是可以同時執(zhí)行多個任務(wù)。
但是并行執(zhí)行的任務(wù)數(shù)量取決于XNU內(nèi)核,是不可控的。比如,如果同時執(zhí)行10個任務(wù),那么10個任務(wù)并不是開啟10個線程,線程會根據(jù)任務(wù)執(zhí)行情況復(fù)用,由系統(tǒng)控制。
獲取隊列
系統(tǒng)提供了兩個隊列,一個是MainDispatchQueue,一個是GlobalDispatchQueue。
前者會將任務(wù)插入主線程的RunLoop當(dāng)中去執(zhí)行,所以顯然是個串行隊列,我們可以使用它來更新UI。
后者則是一個全局的并行隊列,有高、默認(rèn)、低和后臺4個優(yōu)先級。
它們的獲取方式如下:
1 dispatch_queue_t queue = dispatch_get_main_queue();
2
3 dispatch queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY_DEFAULT, 0)
執(zhí)行異步任務(wù)
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2 dispatch_async(queue, ^{
3 //...
4 });
這個代碼片段直接在子線程里執(zhí)行了一個任務(wù)塊。使用GCD方式任務(wù)是立即開始執(zhí)行的
它不像操作隊列那樣可以手動啟動,同樣,缺點也是它的不可控性。
令任務(wù)只執(zhí)行一次
1 + (id)shareInstance {
2 static dispatch_once_t onceToken;
3 dispatch_once(&onceToken, ^{
4 _shareInstance = [[self alloc] init];
5 });
6 }
這種只執(zhí)行一次且線程安全的方式經(jīng)常出現(xiàn)在單例構(gòu)造器當(dāng)中。
任務(wù)組
有時候,我們希望多個任務(wù)同時(在多個線程里)執(zhí)行,再他們都完成之后,再執(zhí)行其他的任務(wù),
于是可以建立一個分組,讓多個任務(wù)形成一個組,下面的代碼在組中多個任務(wù)都執(zhí)行完畢之后再執(zhí)行后續(xù)的任務(wù):
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2 dispatch_group_t group = dispatch_group_create();
3
4 dispatch_group_async(group, queue, ^{ NSLog(@"1"); });
5 dispatch_group_async(group, queue, ^{ NSLog(@"2"); });
6 dispatch_group_async(group, queue, ^{ NSLog(@"3"); });
7 dispatch_group_async(group, queue, ^{ NSLog(@"4"); });
8 dispatch_group_async(group, queue, ^{ NSLog(@"5"); });
9
10 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done"); });延遲執(zhí)行任務(wù)
1 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2 //...
3 });
這段代碼將會在10秒后將任務(wù)插入RunLoop當(dāng)中。
dispatch_asycn和dispatch_sync
先前已經(jīng)有過一個使用dispatch_async執(zhí)行異步任務(wù)的一個例子,下面來看一段代碼:
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2
3 dispatch_async(queue, ^{
4 NSLog(@"1");
5 });
6
7 NSLog(@"2");
這段代碼首先獲取了全局隊列,也就是說,dispatch_async當(dāng)中的任務(wù)被丟到了另一個線程里去執(zhí)行,async在這里的含義是,當(dāng)當(dāng)前線程給子線程分配了block當(dāng)中的任務(wù)之后,當(dāng)前線程會立即執(zhí)行,并不會發(fā)生阻塞,也就是異步的。那么,輸出結(jié)果不是12就是21,因為我們沒法把控兩個線程RunLoop里到底是怎么執(zhí)行的。
類似的,還有一個“同步”方法dispatch_sync,代碼如下:
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2
3 dispatch_sync(queue, ^{
4 NSLog(@"1");
5 });
6
7 NSLog(@"2");
這就意味著,當(dāng)主線程將任務(wù)分給子線程后,主線程會等待子線程執(zhí)行完畢,再繼續(xù)執(zhí)行自身的內(nèi)容,那么結(jié)果顯然就是12了。
需要注意的一點是,這里用的是全局隊列,那如果把dispatch_sync的隊列換成主線程隊列會怎么樣呢:
1 dispatch_queue_t queue = dispatch_get_main_queue();
2 dispatch_sync(queue, ^{
3 NSLog(@"1");
4 });
這段代碼會發(fā)生死鎖,因為:
1.主線程通過dispatch_sync把block交給主隊列后,會等待block里的任務(wù)結(jié)束再往下走自身的任務(wù),
2.而隊列是先進先出的,block里的任務(wù)也在等待主隊列當(dāng)中排在它之前的任務(wù)都執(zhí)行完了再走自己。
這種循環(huán)等待就形成了死鎖。所以在主線程當(dāng)中使用dispatch_sync將任務(wù)加到主隊列是不可取的。
創(chuàng)建隊列
我們可以使用系統(tǒng)提供的函數(shù)獲取主串行隊列和全局并行隊列,當(dāng)然也可以自己手動創(chuàng)建串行和并行隊列,代碼為:
1 dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_SERIAL);
2 dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
在MRC下,手動創(chuàng)建的隊列是需要釋放的
1 dispatch_release(myConcurrentDispatchQueue);
手動創(chuàng)建的隊列和默認(rèn)優(yōu)先級全局隊列優(yōu)先級等同,如果需要修改隊列的優(yōu)先級,需要:
1 dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
2 dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
3 dispatch_set_target_queue(myConcurrentDispatchQueue, targetQueue);
上面的代碼修改隊列的優(yōu)先級為后臺級別,即與默認(rèn)的后臺優(yōu)先級的全局隊列等同。
串行、并行隊列與讀寫安全性
在向串行隊列(SerialDispatchQueue)當(dāng)中加入多個block任務(wù)后,一次只能同時執(zhí)行一個block,如果生成了n個串行隊列,并且向每個隊列當(dāng)中都添加了任務(wù),那么系統(tǒng)就會啟動n個線程來同時執(zhí)行這些任務(wù)。
對于串行隊列,正確的使用時機,是在需要解決數(shù)據(jù)/文件競爭問題時使用它。比如,我們可以令多個任務(wù)同時訪問一塊數(shù)據(jù),這樣會出現(xiàn)沖突,也可以把每個操作都加入到一個串行隊列當(dāng)中,因為串行隊列一次只能執(zhí)行一個線程的任務(wù),所以不會出現(xiàn)沖突。
但是考慮到串行隊列會因為上下文切換而拖慢系統(tǒng)性能,所以我們還是很期望采用并行隊列的,來看下面的示例代碼:
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 2 dispatch_async(queue, ^{ 3 //數(shù)據(jù)讀取 4 }); 5 dispatch_async(queue, ^{ 6 //數(shù)據(jù)讀取2 7 }); 8 dispatch_async(queue, ^{ 9 //數(shù)據(jù)寫入 10 }); 11 dispatch_async(queue, ^{ 12 //數(shù)據(jù)讀取3 13 }); 14 dispatch_async(queue, ^{ 15 //數(shù)據(jù)讀取4 16 }); |
顯然,這5個操作的執(zhí)行順序是我們無法預(yù)期的,我們希望在讀取1和讀取2執(zhí)行結(jié)束后,再執(zhí)行寫入,寫入完成后再執(zhí)行讀取3和讀取4。
為了實現(xiàn)這個效果,這里可以使用GCD的另一個API:
1 dispatch_barrier_async(queue, ^{
2 //數(shù)據(jù)寫入
3 });
這樣就保證的寫入操作的并發(fā)安全性。
對于沒有數(shù)據(jù)競爭的并行操作,則可以使用并行隊列(CONCURRENT)來實現(xiàn)。
JOIN行為
CGD利用dispatch_group_wait來實現(xiàn)多個操作的join行為,代碼如下:
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 2 dispatch_group_t group = dispatch_group_create(); 3 4 dispatch_group_async(group, queue, ^{ 5 sleep(0.5); 6 NSLog(@"1"); 7 }); 8 dispatch_group_async(group, queue, ^{ 9 sleep(1.5); 10 NSLog(@"2"); 11 }); 12 dispatch_group_async(group, queue, ^{ 13 sleep(2.5); 14 NSLog(@"3"); 15 }); 16 17 NSLog(@"aaaaa"); 18 19 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC); 20 if (dispatch_group_wait(group, time) == 0) { 21 NSLog(@"已經(jīng)全部執(zhí)行完畢"); 22 } 23 else { 24 NSLog(@"沒有執(zhí)行完畢"); 25 } 26 27 NSLog(@"bbbbb"); |
這里起了3個異步線程放在一個組里,之后通過dispatch_time_t創(chuàng)建了一個超時時間(2秒),程序之后行,立即輸出了aaaaa,這是主線程輸出的,當(dāng)遇到dispatch_group_wait時,主線程會被掛起,等待2秒,在等待的過程當(dāng)中,子線程分別輸出了1和2,2秒時間達到后,主線程發(fā)現(xiàn)組里的任務(wù)并沒有全部結(jié)束,然后輸出了bbbbb。
在這里,如果超時時間設(shè)置得比較長(比如5秒),那么會在2.5秒時第三個任務(wù)結(jié)束后,立即輸出bbbbb,也就是說,當(dāng)組中的任務(wù)全部執(zhí)行完畢時,主線程就不再被阻塞了。
如果希望永久等待下去,時間可以設(shè)置為DISPATCH_TIME_FOREVER。
并行循環(huán)
類似于C#的PLINQ,OC也可以讓循環(huán)并行執(zhí)行,在GCD當(dāng)中有一個dispatch_apply函數(shù):
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2 dispatch_apply(20, queue, ^(size_t i) {
3 NSLog(@"%lu", i);
4 });
這段代碼讓i并行循環(huán)了20次,如果內(nèi)部處理的是一個數(shù)組,就可以實現(xiàn)對數(shù)組的并行循環(huán)了,它的內(nèi)部是dispatch_sync的同步操作,所以在執(zhí)行這個循環(huán)的過程當(dāng)中,當(dāng)前線程會被阻塞。
暫停和恢復(fù)
使用dispatch_suspend(queue)可以暫停隊列中任務(wù)的執(zhí)行,使用dispatch_result(queue)可以繼續(xù)執(zhí)行被暫停的隊列。
posted on 2014-08-01 09:46 順其自然EVO 閱讀(24950) 評論(2) 編輯 收藏 所屬分類: 測試學(xué)習(xí)專欄