3.1 Grand Central Dispatch(GCD)概要
3.1.1 什么是GCD
Grand Central Dispatch(GCD) 是異步執(zhí)行任務(wù)的技術(shù)之一。一般將應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)級(jí)中實(shí)現(xiàn)。開發(fā)者只需要定義想要執(zhí)行的任務(wù)并追加到適當(dāng)?shù)?Dispatch Queue 中,GCD 就能生成必要的線程并計(jì)劃執(zhí)行任務(wù)。由于線程管理是作為系統(tǒng)的一部分來實(shí)現(xiàn)的,因此可統(tǒng)一管理,也可執(zhí)行任務(wù),這樣就比以前的線程更有效率。
也就是說,GCD 用我們難以置信的非常簡潔的技術(shù)方法,實(shí)現(xiàn)了極為復(fù)雜繁瑣的多線程編程,可以說這是一項(xiàng)劃時(shí)代的技術(shù)。下面是使用了 GCD 源代碼的例子,雖然稍顯抽象,但從中也能感受到 GCD 的威力。
dispatch_async(queue, ^{
/*
*長時(shí)間處理
*例如AR用畫像識(shí)別
*例如數(shù)據(jù)庫訪問
*/
/*
*長時(shí)間處理結(jié)束,主線程使用該處理結(jié)果。
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
*只在主線程可以執(zhí)行的處理
*例如用戶界面刷新
*/
});
});
上面的就是在后臺(tái)線程中執(zhí)行長時(shí)間處理,處理結(jié)束時(shí),主線程使用該處理結(jié)果的源代碼。
dispatch_async(queue, ^{
這僅有一行的代碼表示讓處理在后臺(tái)線程中執(zhí)行。
dispatch_async(dispatch_get_main_queue(), ^{
這樣,僅此一行代碼就能夠讓處理在主線程中執(zhí)行。
另外,大家看到脫字(caret)符號(hào)“^”就能發(fā)現(xiàn),GCD使用了上一章講到的“Blocks”,進(jìn)一步簡化了應(yīng)用程序代碼。
3.1.2 多線程編程
線程到底是什么呢?我們來溫習(xí)一下。先看一下下面的Objective-C源代碼。
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
雖然調(diào)用了幾個(gè)方法,但代碼行基本上是按從上到下的順序執(zhí)行的。
該源代碼通過編譯器轉(zhuǎn)換為如下的CPU命令列(二進(jìn)制代碼)。
000001ac: b590 push {r4, r7, lr}
000001ae: f240019c movw r1, :lower16:0x260-0x1c0+0xfffffffc
000001b2: af01 add r7, sp, #4
000001b4: f2c00100 movt r1, :upper16:0x260-0x1c0+0xfffffffc
000001b8: f24010be movw r0, :lower16:0x384-0x1c2+0xfffffffc
000001bc: f2c00100 movt r0, :upper16:0x384-0x1c0+0xfffffffc
000001c0: 4479 add r1, pc
000001c2: 4478 add r0, pc
000001c4: 6809 add r1, [r1, #0]
000001c6: 6800 add r0, [r0, #0]
...
匯集CPU命令行和數(shù)據(jù),將其作為一個(gè)應(yīng)用程序安裝到Mac或iPhone上。CPU從應(yīng)用程序指定的地址開始,一個(gè)一個(gè)地執(zhí)行CPU命令列。先執(zhí)行l(wèi)ac的命令行push,接著向后移動(dòng),執(zhí)行地址lae的命令行movw,這樣不斷循環(huán)下去。
由于一個(gè)CPU一次只能執(zhí)行一個(gè)命令,不能執(zhí)行某處分開的并列的兩個(gè)命令,因此通過CPU執(zhí)行的CPU命令列就好比一條無分叉的達(dá)到,其執(zhí)行不會(huì)出現(xiàn)分歧。
這里所說的“一個(gè)CPU執(zhí)行的CPU命令列為一條無分叉路徑”即為“線程”。
多線程是指在軟件或硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。通俗講就是在同步或異步的情況下,開辟新線程,進(jìn)行線程間的切換,以及對(duì)線程進(jìn)行合理的調(diào)度,做到優(yōu)化提升程序性能的目的。

盡管多線程編程極易發(fā)生各種問題,但是呢也應(yīng)當(dāng)使用多線程編程,因?yàn)槭褂枚嗑€程編程可以保證應(yīng)用程序的相應(yīng)性能。
應(yīng)用程序在啟動(dòng)時(shí),通過主線程描繪用戶界面、處理觸摸屏幕的事件等。如果在該主線程中進(jìn)行長時(shí)間的處理,如數(shù)據(jù)庫訪問等,就會(huì)妨礙主線程的執(zhí)行(阻塞)。這樣會(huì)妨礙主線程中被稱為RunLoop的主循環(huán)的執(zhí)行,從而導(dǎo)致不能更新用戶界面、應(yīng)用程序的畫面長時(shí)間停滯等問題。
使用多線程編程,在執(zhí)行長時(shí)間處理時(shí)仍可保證用戶界面的響應(yīng)性能。如下圖

GCD大大簡化了偏于復(fù)雜的多線程編程的源代碼。
3.2 GCD的API
3.2.1 Dispatch Queue
蘋果官方對(duì)GCD的說明:
開發(fā)者要做的只是定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中。
dispatch_async(queue,^{
/*
* 想執(zhí)行的任務(wù)
*/
});
上面的代碼用了Block語法“定義想執(zhí)行的任務(wù)”,通過dispatch_async函數(shù)“追加”賦值在變量queue的“Dispatch Queue中”。僅這樣就可使指定的Block在另一線程中執(zhí)行。

另外在執(zhí)行處理時(shí)存在兩種Dispatch Queue,一種是等待現(xiàn)在執(zhí)行中處理的Serial Dispatch Queue,另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue。如下表:
| Dispatch Queue 的種類 | 說明 |
|---|---|
| Serial Dispatch Queue | 等待現(xiàn)在執(zhí)行中處理結(jié)束 |
| Concurrent Dispatch Queue | 不等待現(xiàn)在執(zhí)行中處理結(jié)束 |

舉例來區(qū)分一下兩種調(diào)度隊(duì)列的區(qū)別:
有下面8個(gè)任務(wù)等待執(zhí)行,
dispatch_async(queue,blk0)
dispatch_async(queue,blk1)
dispatch_async(queue,blk2)
dispatch_async(queue,blk3)
dispatch_async(queue,blk4)
dispatch_async(queue,blk5)
dispatch_async(queue,blk6)
dispatch_async(queue,blk7)
如果queue使用Serial Dispatch Queue,則同時(shí)執(zhí)行的處理數(shù)只有一個(gè),blk0執(zhí)行結(jié)束才能執(zhí)行blk1,blk1執(zhí)行結(jié)束才能執(zhí)行blk2,blk0-> blk1-> blk2-> blk3-> blk4-> blk5-> blk6-> blk7順序執(zhí)行。
如果queue使用Concurrent Dispatch Queue,這樣不用等待現(xiàn)在執(zhí)行中的處理結(jié)束,可以并行執(zhí)行多個(gè)處理,但并行執(zhí)行的處理數(shù)取決于iOS和OS X的CPU核數(shù)以及CPU負(fù)荷等當(dāng)前系統(tǒng)狀態(tài)。所謂“并發(fā)執(zhí)行”,就是使用多個(gè)線程同時(shí)執(zhí)行多個(gè)處理。
前面的代碼如下表,在多個(gè)線程中執(zhí)行Block。
| 線程0 | 線程1 | 線程2 | 線程3 |
|---|---|---|---|
| blk0 | blk1 | blk2 | blk3 |
| blk4 | blk6 | blk5 | |
| blk7 |
如果才能得到這些Dispatch Queue?方法有兩種。
3.2.2 dispatch_queue_create
說明dispatch_queue_create函數(shù)前先看一下Serial Dispatch Queue生成個(gè)數(shù)的注意事項(xiàng)。
如前所述,Concurrent Dispatch Queue 并行執(zhí)行多個(gè)追加處理, Serial Dispatch Queue同時(shí)只能執(zhí)行1個(gè)追加處理。雖然它們都受到系統(tǒng)資源的限制,但用dispatch_queue_create函數(shù)可以生成任意多個(gè)Dispatch Queue。

3.2.3 Main Dispatch Queue/Global Dispatch Queue
第二種方法就是獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue。
不用特意生成Dispatch Queue,系統(tǒng)也會(huì)給我們提供幾個(gè),那就是Main Dispatch Queue 和 Global Dispatch Queue。
Main Dispatch Queue 是在主線程中執(zhí)行的,因?yàn)橹骶€程只有1個(gè),所以它自然就是Serial Dispatch Queue。
追加到Main Dispatch Queue的處理會(huì)在主線程的RunLoop中執(zhí)行。所以要將用戶界面更新等一些必須再主線程中執(zhí)行的處理追加到這里來使用。
Global Dispatch Queue是所有應(yīng)用程序都能夠使用的Concurrent Dispatch Queue。它有4個(gè)執(zhí)行優(yōu)先級(jí)。
| 名稱 | Dispatch Queue 的種類 | 說明 |
|---|---|---|
| Main Dispatch Queue | Serial Dispatch Queue | 主線程執(zhí)行 |
| Global Dispatch Queue (High Priority) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級(jí):高(最高優(yōu)先) |
| Global Dispatch Queue (Default Priority) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級(jí):默認(rèn) |
| Global Dispatch Queue (Low Priority) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級(jí):低 |
| Global Dispatch Queue (Background Priority) | Concurrent Dispatch Queue | 執(zhí)行優(yōu)先級(jí):后臺(tái) |
各種 Dispatch Queue 的獲取方法如下:
// Main Dispatch Queue 的獲取方法
dispatch_queue_t mainDispatchQueue = dispath_get_main_queue();
// Global Dispatch Queue 的獲取方法
dispatch_queue_t globalDispatchQueueHigh = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0) //高優(yōu)先級(jí)
dispatch_queue_t globalDispatchQueueDefault = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) //默認(rèn)優(yōu)先級(jí)
dispatch_queue_t globalDispatchQueueLow = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0) //低優(yōu)先級(jí)
dispatch_queue_t globalDispatchQueueBackgroud = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0) //高優(yōu)先級(jí)
以下舉例兩種聯(lián)合使用:
//在默認(rèn)優(yōu)先級(jí)的Global Dispatch Queue中執(zhí)行block
dispatch_async(dispatch_get_global_queue,0),^{
// 可并行執(zhí)行的處理
//在Main Dispatch Queue中執(zhí)行block
dispatch_async(dispatch_get_main_queue(),^{
//只能在主線程中執(zhí)行的代碼,如刷新UI
});
});
3.2.4 dispatch_set_target_queue
此3.2.4小節(jié)部分摘抄自作者 lltree的簡書。感謝原作者“ lltree”。
- 使用dispatch_set_target_queue可以更改Dispatch Queue的執(zhí)行優(yōu)先級(jí)
dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_set_target_queue(serialQueue, globalQueue);
第一個(gè)參數(shù)為要設(shè)置優(yōu)先級(jí)的queue,第二個(gè)參數(shù)是參照物,既將第一個(gè)queue的優(yōu)先級(jí)和第二個(gè)queue的優(yōu)先級(jí)設(shè)置一樣。
-
dispatch_set_target_queue還能夠創(chuàng)建隊(duì)列的層次體系,當(dāng)我們想讓不同隊(duì)列中的任務(wù)同步的執(zhí)行時(shí),我們可以創(chuàng)建一個(gè)串行隊(duì)列,然后將這些隊(duì)列的target指向新創(chuàng)建的隊(duì)列即可,比如啊啊啊.png
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目標(biāo)隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行隊(duì)列
dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
//設(shè)置參考
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue2, ^{
NSLog(@"job3 in");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"job3 out");
});
dispatch_async(queue2, ^{
NSLog(@"job2 in");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"job2 out");
});
dispatch_async(queue1, ^{
NSLog(@"job1 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"job1 out");
});
輸出結(jié)果:
[3491:1491712] job3 in
[3491:1491712] job3 out
[3491:1491712] job2 in
[3491:1491712] job2 out
[3491:1491712] job1 in
[3491:1491712] job1 out
通過打印的結(jié)果說明我們?cè)O(shè)置了queue1和queue2隊(duì)列以targetQueue隊(duì)列為參照對(duì)象,那么queue1和queue2中的任務(wù)將按照targetQueue的隊(duì)列處理。
適用場景:
一般都是把一個(gè)任務(wù)放到一個(gè)串行的queue中,如果這個(gè)任務(wù)被拆分了,被放置到多個(gè)串行的queue中,但實(shí)際還是需要這個(gè)任務(wù)同步執(zhí)行,那么就會(huì)有問題,因?yàn)槎鄠€(gè)串行queue之間是并行的。這時(shí)候dispatch_set_target_queue將起到作用。
3.2.5 dispatch_after
經(jīng)常會(huì)有這樣的情況:想在3秒后執(zhí)行處理。這種想在指定時(shí)間后執(zhí)行處理的情況,可使用dispatch_after函數(shù)實(shí)現(xiàn)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_ROW, 3ull * NSEC_PER_SEC);
dispatch_after (time ,dispatch_get_main_queue(),^{
//等待三秒之后要執(zhí)行的操作
});
需要注意的是,dispatch_after函數(shù)并不是在指定時(shí)間后執(zhí)行處理,而只是在指定時(shí)間追加處理到Dispatch Queue。此代碼與3秒后用dispatch_async函數(shù)追加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有大量處理追加或主線程的處理本身有延遲時(shí),這個(gè)時(shí)間會(huì)更長。
雖然在有嚴(yán)格時(shí)間的要求下使用時(shí)會(huì)出現(xiàn)問題,但在想大致延遲執(zhí)行處理時(shí),該函數(shù)是非常有效的。
另外,第二個(gè)參數(shù)指定要追加處理的 Dispatch Queue,第三個(gè)參數(shù)指定要執(zhí)行處理的Block。
第一個(gè)參數(shù)是指定時(shí)間的 dispatch_time_t 類型的值。該值使用 dispatch_time 函數(shù)或 dispatch_walltime 函數(shù)作成。第一個(gè)參數(shù)經(jīng)常使用DISPATCH_TIME_NOW。這表示現(xiàn)在的時(shí)間。下面代碼表示獲得從現(xiàn)在開始1秒后的dispatch_time_t類型的值。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
數(shù)值和 NSEC_PER_SEC 的乘積得到單位為毫微秒的數(shù)值。“ull”是C語言的數(shù)值字面量,是顯式表明類型時(shí)使用的字符串(表示“unsigned long long”)。如果使用NESC_PER_MSEC 則以毫秒為單位計(jì)算。下面代碼表示獲取從現(xiàn)在開始150毫秒后時(shí)間的值。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
dispatch_time函數(shù)通常用于計(jì)算相對(duì)時(shí)間,而dispatch_walltime函數(shù)用于計(jì)算絕對(duì)時(shí)間。
3.2.6 Dispatch Group
有時(shí)候我們會(huì)有這種需求,在剛進(jìn)去一個(gè)頁面需要發(fā)送兩個(gè)請(qǐng)求,并且某種特定操作必須在兩個(gè)請(qǐng)求都結(jié)束(成功或失?。┑臅r(shí)候才會(huì)執(zhí)行,最low的辦法第二個(gè)請(qǐng)求嵌套在第一個(gè)請(qǐng)求結(jié)果后在發(fā)送,在第二個(gè)請(qǐng)求結(jié)束后再執(zhí)行操作。
還有就是只使用一個(gè) Serial Dispatch Queue,把想要執(zhí)行的操作全部追加到這個(gè) Serial Dispatch Queue 中并在最后追加結(jié)束處理。但在使用 Concurrent Dispatch Queue 或 同時(shí)使用多個(gè) Dispatch Queue 時(shí),代碼會(huì)變得很復(fù)雜。
這種情況可以使用 Dispatch Group 。
我們將ABC三個(gè)任務(wù)block追加到 Global Dispatch Queue,ABC全部執(zhí)行完,會(huì)執(zhí)行 dispatch_group_notify中的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_creat();
dispatch_group_async(group,queue,^{NSLog(@"執(zhí)行任務(wù)A");});
dispatch_group_async(group,queue,^{NSLog(@"執(zhí)行任務(wù)B");});
dispatch_group_async(group,queue,^{NSLog(@"執(zhí)行任務(wù)C");});
dispatch_group_notify(group,dispatch_get_main_queue(),^{
NSLog(@"執(zhí)行最終的特定操作");
});
dispatch_release(group);
執(zhí)行結(jié)果如下:
執(zhí)行任務(wù)A
執(zhí)行任務(wù)B
執(zhí)行任務(wù)C
執(zhí)行最終的特定操作
上面的
dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"執(zhí)行最終的特定操作");
});
操作還可以更改為
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
dispatch_group_wait第二個(gè)參數(shù)指定為等待的時(shí)間(超時(shí)),屬于dispatch_time_t類型,在這里使用DISPATCH_TIME_FOREVER,意味著永久等待。如果dispatch group的處理尚未結(jié)束,就會(huì)一直等待,中途不能取消。
如果指定等待時(shí)間為1秒如下:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NESC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0) {
// dispatch group的全部處理執(zhí)行結(jié)束
}else {
// dispatch groupe的某一處理還在執(zhí)行中
};
3.2.7 dispatch_barrier_async
dispatch_barrier_async函數(shù)的作用與barrier的意思相同,在進(jìn)程管理中起到一個(gè)柵欄的作用,它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行之后,barrier函數(shù)之后的操作才會(huì)得到執(zhí)行,該函數(shù)需要同dispatch_queue_create函數(shù)生成的concurrent Dispatch Queue隊(duì)列一起使用。
作用:
1.實(shí)現(xiàn)高效率的數(shù)據(jù)庫訪問和文件訪問
2.避免數(shù)據(jù)競爭
//同dispatch_queue_create函數(shù)生成的concurrent Dispatch Queue隊(duì)列一起使用
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
輸出結(jié)果:1 2 --> barrier -->3 4 其中12 與 34 由于并行處理先后順序不定。
3.2.8 dispatch_sync
dispatch_sync 函數(shù)的“async”意味著“非同步”(asynchronous),就是將指定的Block“非同步”地追加到指定的 Dispatch Queue中。dispatch_async 函數(shù)不做任何等待。
有“async”,也有“sync”,即dispatch_sync函數(shù)。它意味著“同步”(synchronous),再追加Block結(jié)束之前,dispatch_sync函數(shù)會(huì)一直等待。
假設(shè)一種情況:執(zhí)行Main Dispatch Queue時(shí),使用另外的線程Global Dispatch Queue進(jìn)行處理,處理結(jié)束后立即使用所得到的結(jié)果。在這種情況下就要使用dispatch_sync函數(shù)。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{NSLog(@"Hello?");});
一旦調(diào)用dispatch_sync函數(shù),在指定處理執(zhí)行結(jié)束前,該函數(shù)不會(huì)返回。它可以簡化代碼,可以說是簡易版的dispatch_group_wait函數(shù)。
因?yàn)楹唵危匀菀滓饐栴},如果在主線程執(zhí)行以下代碼就會(huì)死鎖。
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?");});
上面代碼在Main Dispatch Queue即主線程中執(zhí)行指定的Block,并等待其執(zhí)行結(jié)束。而其實(shí)在主線程中正在執(zhí)行這些代碼,所以無法執(zhí)行追加到Main Dispatch Queue的Block。
所以大家以后深思熟慮以后再使用dispatch_sync函數(shù)同步等待處理執(zhí)行的API,不然稍有不慎就導(dǎo)致程序死鎖。
3.2.9 dispatch_apply
這個(gè)函數(shù)可以給定指定的次數(shù)將block追加到指定的Dispatch Queue中,并且等待全部結(jié)束處理執(zhí)行。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply(10,queue,^(size_t index){
NSLog(@“%zu”,idnex);
});
NSLog(@“done”);
//執(zhí)行結(jié)果:4,1,0,3,5,2,6,8,9,7,done
第一個(gè)參數(shù)是重復(fù)次數(shù),第二個(gè)參數(shù)是追加對(duì)象的dispatch queue,第三個(gè)參數(shù)是追加的處理。dispatch_apply可以做遍歷數(shù)組的操作,不必一個(gè)一個(gè)寫for循環(huán)。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply([array count],queue,^(size_t index)){
NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
}
3.2.10 dispatch_suspend / dispatch_resume
當(dāng)追加大量處理到Dispatch Queue時(shí),在追加處理的過程中有時(shí)希望不執(zhí)行已追加的處理。例如演算結(jié)果被Block截獲時(shí),一些處理會(huì)對(duì)這個(gè)演算結(jié)果造成影響。
在這種情況下,只要掛起Dispatch Queue即可。當(dāng)可以執(zhí)行時(shí)再恢復(fù)。
dispatch_suspend 函數(shù)掛起指定的 Dispatch Queue。
dispatch_suspend(queue);
dispatch_resume 函數(shù)恢復(fù)指定的 Dispatch Queue。
dispatch_resume(queue);
3.2.11 Dispatch Semaphore
此節(jié)3.2.11部分摘錄于CSDN的一篇博客 iOS GCD中級(jí)篇 - dispatch_semaphore(信號(hào)量)的理解及使用。感謝原作者“那一抹風(fēng)情”。
理解這個(gè)概念之前,先拋出一個(gè)問題
問題描述:
假設(shè)現(xiàn)在系統(tǒng)有兩個(gè)空閑資源可以被利用,但同一時(shí)間卻有三個(gè)線程要進(jìn)行訪問,這種情況下,該如何處理呢?
或者
我們要下載很多圖片,并發(fā)異步進(jìn)行,每個(gè)下載都會(huì)開辟一個(gè)新線程,可是我們又擔(dān)心太多線程肯定cpu吃不消,那么我們這里也可以用信號(hào)量控制一下最大開辟線程數(shù)。
定義:
1、信號(hào)量:就是一種可用來控制訪問資源的數(shù)量的標(biāo)識(shí),設(shè)定了一個(gè)信號(hào)量,在線程訪問之前,加上信號(hào)量的處理,則可告知系統(tǒng)按照我們指定的信號(hào)量數(shù)量來執(zhí)行多個(gè)線程。
其實(shí),這有點(diǎn)類似鎖機(jī)制了,只不過信號(hào)量都是系統(tǒng)幫助我們處理了,我們只需要在執(zhí)行線程之前,設(shè)定一個(gè)信號(hào)量值,并且在使用時(shí),加上信號(hào)量處理方法就行了。
2、信號(hào)量主要有3個(gè)函數(shù),分別是:
//創(chuàng)建信號(hào)量,參數(shù):信號(hào)量的初值,如果小于0則會(huì)返回NULL
dispatch_semaphore_create(信號(hào)量值)
//等待降低信號(hào)量
dispatch_semaphore_wait(信號(hào)量,等待時(shí)間)
//提高信號(hào)量
dispatch_semaphore_signal(信號(hào)量)
注意,正常的使用順序是先降低然后再提高,這兩個(gè)函數(shù)通常成對(duì)使用。(具體可參考下面的代碼示例)
3、那么就開頭提的問題,我們用代碼來解決
-(void)dispatchSignal{
//crate的value表示,最多幾個(gè)資源可訪問
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任務(wù)1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});<br>
//任務(wù)2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});<br>
//任務(wù)3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
執(zhí)行結(jié)果:
總結(jié):由于設(shè)定的信號(hào)值為2,先執(zhí)行兩個(gè)線程,等執(zhí)行完一個(gè),才會(huì)繼續(xù)執(zhí)行下一個(gè),保證同一時(shí)間執(zhí)行的線程數(shù)不超過2。
這里我們擴(kuò)展一下,假設(shè)我們?cè)O(shè)定信號(hào)值=1
dispatch_semaphore_create(1)
那么結(jié)果就是:
如果設(shè)定信號(hào)值=3
dispatch_semaphore_create(3)
那么結(jié)果就是:
其實(shí)設(shè)定為3,就是不限制線程執(zhí)行了,因?yàn)橐还膊胖挥?個(gè)線程。
3.2.12 dispatch_once
dispatch_once函數(shù)是保證在程序執(zhí)行中只執(zhí)行一次指定處理的API。
使用dispatch_once函數(shù)為:
static dispatch_once_ onceToken;
dispatch_once( &onceToken,^{
// 初始化操作
對(duì)象A =[ [對(duì)象A alloc] init];
});
通過dispatch_once創(chuàng)建的即使在多線程環(huán)境下執(zhí)行也百分百安全。
這就是所說的單例模式,在生成單例對(duì)象時(shí)使用。
3.2.13 Dispatch I/O
在讀取較大文件時(shí),如果將文件分成合適的大小并使用Global Dispatch Queue 并發(fā)讀取的話,應(yīng)該會(huì)比一般的讀取速度快不少?,F(xiàn)在的輸入/輸出硬件已經(jīng)可以做到一次使用多個(gè)線程更快地并發(fā)讀取了。能實(shí)現(xiàn)這一功能的就是Dispatch I/O 和Dispatch Data。
通過Dispatch I/O讀寫文件時(shí),使用Global Dispatch Queue將1個(gè)文件按某個(gè)大小read/write。
dispatch_async(queue, ^{ /* 讀取 0 ~ 8080 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 8081 ~ 16383 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 16384 ~ 24575 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 24576 ~ 32767 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 32768 ~ 40959 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 40960 ~ 49191 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 49192 ~ 57343 字節(jié)*/ });
dispatch_async(queue, ^{ /* 讀取 57344 ~ 65535 字節(jié)*/ });
像上面這樣,將文件分割為一塊一塊地進(jìn)行讀取處理。分割讀取的數(shù)據(jù)通過使用Dispatch Data 可更為簡單地進(jìn)行結(jié)合和分割。
請(qǐng)看下面蘋果中使用Dispatch I/O 和 Dispatch Data的例子。下面的代碼摘自Apple System Log API里的源代碼(地址:http://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c)
static int
_asl_auxiliary(aslmsg msg, const char *title, const char *uti, const char *url, int *out_fd)
{
asl_msg_t *merged_msg;
asl_msg_aux_t aux;
asl_msg_aux_0_t aux0;
fileport_t fileport;
kern_return_t kstatus;
uint32_t outlen, newurllen, len, where;
int status, fd, fdpair[2];
caddr_t out, newurl;
dispatch_queue_t pipe_q;
dispatch_io_t pipe_channel;
dispatch_semaphore_t sem;
/* ..... 此處省略若干代碼.....*/
// 創(chuàng)建串行隊(duì)列
pipe_q = dispatch_queue_create("PipeQ", NULL);
// 創(chuàng)建 Dispatch I/O
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
// 該函數(shù)設(shè)定一次讀取的大?。ǚ指畲笮。? dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
//
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0) // err等于0 說明讀取無誤
{
// 讀取完“單個(gè)文件塊”的大小
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
// 定義一個(gè)字節(jié)數(shù)組bytes
const char *bytes = NULL;
char *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
asl_msg_release(merged_msg);
dispatch_release(md);
}
}
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});
}
設(shè)置一次讀取的最大字節(jié)
void dispatch_io_set_high_water( dispatch_io_t channel, size_t high_water);
設(shè)置一次讀取的最小字節(jié)
void dispatch_io_set_low_water( dispatch_io_t channel, size_t low_water);
dispatch_io_read 函數(shù)使用Global Dispatch Queue 開始并發(fā)讀取。每當(dāng)各個(gè)分割的文件塊讀取結(jié)束時(shí),將含有文件塊數(shù)據(jù)的 Dispatch Data(這里指pipedata) 傳遞給 “dispatch_io_read 函數(shù)指定的讀取結(jié)束時(shí)回調(diào)用的block”,這個(gè)block拿到每一塊讀取好的Dispatch Data(這里指pipe data),然后進(jìn)行合并處理。
如果想提高文件讀取速度,可以嘗試使用 Dispatch I/O.
