GCD筆記
總結(jié)一下多線程部分,最強(qiáng)大的無疑是GCD,那么先從這一塊部分講起.
Dispatch Queue的種類
GCD 提供有 dispatch queues 來處理代碼塊,這些隊(duì)列管理你提供給 GCD 的任務(wù)并用 FIFO 順序執(zhí)行這些任務(wù)。這就保證了第一個被添加到隊(duì)列里的任務(wù)會是隊(duì)列中第一個開始的任務(wù),而第二個被添加的任務(wù)將第二個開始,如此直到隊(duì)列的終點(diǎn)。
所有的調(diào)度隊(duì)列(dispatch queues)自身都是線程安全的,你能從多個線程并行的訪問它們。當(dāng)你了解了調(diào)度隊(duì)列如何為你自己代碼的不同部分提供線程安全后,GCD的優(yōu)點(diǎn)就是顯而易見的。關(guān)于這一點(diǎn)的關(guān)鍵是選擇正確類型的調(diào)度隊(duì)列和正確的調(diào)度函數(shù)來提交你的工作。
在本節(jié)你會看到兩種調(diào)度隊(duì)列,都是由 GCD 提供的
- Serial Dispatch Queue 等待現(xiàn)在執(zhí)行中處理結(jié)束,也就是我們常說的
串行隊(duì)列串行隊(duì)列中的任務(wù)一次執(zhí)行一個,每個任務(wù)只在前一個任務(wù)完成時才開始。而且,你不知道在一個 Block 結(jié)束和下一個開始之間的時間長度.這些任務(wù)的執(zhí)行時機(jī)受到 GCD 的控制;唯一能確保的事情是 GCD 一次只執(zhí)行一個任務(wù),并且按照我們添加到隊(duì)列的順序來執(zhí)行。 - Concurrent Dispatch Queue 不等待現(xiàn)在執(zhí)行中處理結(jié)束,也就是我們常說的
并行隊(duì)列.在并發(fā)隊(duì)列中的任務(wù)能得到的保證是它們會按照被添加的順序開始執(zhí)行,但這就是全部的保證了。任務(wù)可能以任意順序完成,你不會知道何時開始運(yùn)行下一個任務(wù),或者任意時刻有多少 Block 在運(yùn)行。再說一遍,這完全取決于 GCD 。
GCD眾多API解析
1.Dispatch_queue_creat
- 生成一個串行隊(duì)列,按照順序執(zhí)行隊(duì)列里的任務(wù)
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_creat("com.example.gcd.MyConcurrentDispatchQueue",NULL);
- 生成一個并行隊(duì)列,同時執(zhí)行隊(duì)列里的任務(wù)
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_creat("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);
注意:非ARC情況下,通過creat函數(shù)生成的隊(duì)列,必須程序員自己來進(jìn)行內(nèi)存管理,通過
dispatch_release()或者dispatch_retain()來進(jìn)行內(nèi)存管理,ARC情況下,無須我們自己處理
2.Main Dispatch Queue/Global Dispatch Queue
Global Dispatch Queue 有4個優(yōu)先級,分別是高優(yōu)先級(
High Priority) \默認(rèn)優(yōu)先級(Default Priority)\低優(yōu)先級(Low Priority)和后臺優(yōu)先級(Background Priority).
首先,系統(tǒng)提供給你一個叫做 主隊(duì)列(main queue) 的特殊隊(duì)列。和其它串行隊(duì)列一樣,這個隊(duì)列中的任務(wù)一次只能執(zhí)行一個。然而,它能保證所有的任務(wù)都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程。這個隊(duì)列就是用于發(fā)生消息給 UIView 或發(fā)送通知的。
系統(tǒng)同時提供給你好幾個并發(fā)隊(duì)列。它們叫做 全局調(diào)度隊(duì)列(Global Dispatch Queues) 。目前的四個全局隊(duì)列有著不同的優(yōu)先級:background、low、default 以及 high。要知道,Apple 的 API 也會使用這些隊(duì)列,所以你添加的任何任務(wù)都不會是這些隊(duì)列中唯一的任務(wù)。
最后,你也可以創(chuàng)建自己的串行隊(duì)列或并發(fā)隊(duì)列。這就是說,至少有五個隊(duì)列任你處置:主隊(duì)列、四個全局調(diào)度隊(duì)列,再加上任何你自己創(chuàng)建的隊(duì)列。
以上是調(diào)度隊(duì)列的大框架!
GCD 的“藝術(shù)”歸結(jié)為選擇合適的隊(duì)列來調(diào)度函數(shù)以提交你的工作,下面介紹一下系統(tǒng)提供的眾多函數(shù)
3.Dispatch_set_target_queue
功能:變更生成的Dispatch Queue 的
執(zhí)行優(yōu)先級要使用該函數(shù).
4.Dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ <#code to be executed after a specified delay#> });
注意:
dispatch_after函數(shù)并不是在指定時間后執(zhí)行處理,而是在指定時間追加處理到Dispatch Queue.dispatch_after工作起來就像一個延遲版的dispatch_async。你依然不能控制實(shí)際的執(zhí)行時間,且一旦dispatch_after返回也就不能再取消它。
- 第一個參數(shù)是指定時間,用的
dispatch_time_t類型的值.該值使用dispatch_time函數(shù)或dispatch_walltime函數(shù)作成.
dispatch_time用于計(jì)算相對時間
dispatch_walltime用于計(jì)算絕對時間
ull代表unsigned long long
數(shù)值和NSEC_PER_SEC的乘積得到單位為秒的數(shù)值
數(shù)值和NSEC_PER_MSEC的乘積得到單位為毫秒的數(shù)值DISPATCH_TIME_NOW表示現(xiàn)在的時間關(guān)于dispatch_time,第一個參數(shù)通常使用DISPATCH_TIME_NOW,它是一個表示dispatch_time_t的宏,表示從現(xiàn)在開始算起 - 第二個參數(shù)是第一個參數(shù)之后經(jīng)歷的時長。而我們通常用的
NSEC_PER_SEC也是一個宏,還有其他的宏:
#define NSEC_PER_SEC 1000000000ull //每秒有多少納秒
#define NSEC_PER_MSEC 1000000ull //每毫秒有多少納秒
#define USEC_PER_SEC 1000000ull //每秒有多少微秒
#define NSEC_PER_USEC 1000ull //每微秒有多少納秒
因?yàn)?秒鐘有NSEC_PER_SEC(也即1000000000)納秒,所以NSEC_PER_SEC其實(shí)就相當(dāng)于1秒,那么兩秒就是2 *NSEC_PER_SEC;
同理,NSEC_PER_MSEC就相當(dāng)于是1毫秒,那么2秒鐘,就應(yīng)該是2000 * NSEC_PER_MSEC;
USEC_PER_SEC也相當(dāng)于1毫秒,2秒鐘就是2000 *USEC_PER_SEC
NSEC_PER_USEC相當(dāng)于 1微妙,所以要表示1秒鐘就是1000000 NSEC_PER_USEC。
所以1秒后執(zhí)行某段代碼可以這樣寫:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"哈哈"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ NSLog(@"嘿嘿"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000 * USEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"嗯嗯"); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1000000 * NSEC_PER_USEC)), dispatch_get_main_queue(), ^{ NSLog(@"呃呃"); });
這個API的作用與下面這個方法類似:
[self performSelector:@selector(testClick:) withObject:nil afterDelay:2.0];
不知道何時適合使用 dispatch_after ?
- 自定義串行隊(duì)列:在一個自定義串行隊(duì)列上使用 dispatch_after 要小心。你最好堅(jiān)持使用主隊(duì)列。
- 主隊(duì)列(串行):是使用 dispatch_after 的好選擇;Xcode 提供了一個不錯的自動完成模版。
- 并發(fā)隊(duì)列:在并發(fā)隊(duì)列上使用 dispatch_after 也要小心;你會這樣做就比較罕見。還是在主隊(duì)列做這些操作吧。
5.Dispatch Group
使用場景:在多個異步任務(wù)全部執(zhí)行完畢后,執(zhí)行某個任務(wù)。如果用同步任務(wù)或串行隊(duì)列,就沒有意義了,要謹(jǐn)記。
這里有兩種實(shí)現(xiàn)方式:
方式一 利用dispatch_group_async和dispatch_group_notify配合.
方式二 利用dispatch_group_enter 、dispatch_group_leave和
dispatch_group_notify配合,其中需要注意的是有dispatch_group_enter就必定有一個dispatch_group_leave與之對應(yīng),否則可能會出現(xiàn)令你意想不到的崩潰。
Dispatch Group 會在整個組的任務(wù)都完成時通知你。這些任務(wù)可以是同步的,也可以是異步的,即便在不同的隊(duì)列也行。而且在整個組的任務(wù)都完成時,Dispatch Group 可以用同步的或者異步的方式通知你。因?yàn)橐O(jiān)控的任務(wù)在不同隊(duì)列,那就用一個 dispatch_group_t 的實(shí)例來記下這些不同的任務(wù)。
當(dāng)組中所有的事件都完成時,GCD 的 API 提供了兩種通知方式。
第一種是 dispatch_group_wait ,它會阻塞當(dāng)前線程,直到組里面所有的任務(wù)都完成或者等到某個超時發(fā)生。這恰好是你目前所需要的。
在我們轉(zhuǎn)向另外一種使用 Dispatch Group 的方式之前,先看一個簡要的概述,關(guān)于何時以及怎樣使用有著不同的隊(duì)列類型的 Dispatch Group :
- 自定義串行隊(duì)列:它很適合當(dāng)一組任務(wù)完成時發(fā)出通知。
- 主隊(duì)列(串行):它也很適合這樣的情況。但如果你要同步地等待所有工作地完成,那你就不應(yīng)該使用它,因?yàn)槟悴荒茏枞骶€程。然而,異步模型是一個很有吸引力的能用于在幾個較長任務(wù)(例如網(wǎng)絡(luò)調(diào)用)完成后更新 UI 的方式。
- 并發(fā)隊(duì)列:它也很適合 Dispatch Group 和完成時通知。
dispatch_group_notify 以異步的方式工作。當(dāng) Dispatch Group 中沒有任何任務(wù)時,它就會執(zhí)行其代碼,那么 completionBlock 便會運(yùn)行。你還指定了運(yùn)行 completionBlock 的隊(duì)列,此處,主隊(duì)列就是你所需要的。
Dispatch_group_wait該API依然是與dispatch_group配合使用。它會阻塞當(dāng)前所在的線程,直到前面的blocks 執(zhí)行完成,或者超時的時候返回。該方法會同步的等待之前比較的block 對象們完成,如果在給定的時間內(nèi)沒有完成,該方法就會返回。如果在給定的時間超時前完成,則返回0,否則就返回一個非零的值。
示例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 5; i++)
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:i];
NSLog(@"并發(fā)%d結(jié)束----線程:%@", i,[NSThread currentThread]);
});
}
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long result = dispatch_group_wait(group, time);
if (result) {
NSLog(@"超時了");
} else {
NSLog(@"執(zhí)行完畢");
}// 打印結(jié)果是:
2016-07-12 14:04:17.980 PractiseProject[4537:155453] 并發(fā)0結(jié)束----線程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}
2016-07-12 14:04:18.981 PractiseProject[4537:155441] 并發(fā)1結(jié)束----線程:<NSThread: 0x7fbbe8f5ee60>{number = 4, name = (null)}
2016-07-12 14:04:19.980 PractiseProject[4537:155338] 超時了
2016-07-12 14:04:19.981 PractiseProject[4537:155456] 并發(fā)2結(jié)束----線程:<NSThread: 0x7fbbe8f5d260>{number = 5, name = (null)}
2016-07-12 14:04:20.985 PractiseProject[4537:155464] 并發(fā)3結(jié)束----線程:<NSThread: 0x7fbbe8f60e90>{number = 6, name = (null)}
2016-07-12 14:04:21.984 PractiseProject[4537:155453] 并發(fā)4結(jié)束----線程:<NSThread: 0x7fbbe8e115e0>{number = 3, name = (null)}
由于dispatch_group_wait會阻塞線程,在dispatch_group_wait后面的代碼并不會執(zhí)行,如果我們在主線程中執(zhí)行上面的代碼段,則會阻塞UI界面。所以我們應(yīng)該在子線程中執(zhí)行上面的代碼片段(用一個dispatch_async包起來)。如果我們設(shè)置wait的時間為永遠(yuǎn)的話,由于在子線程中執(zhí)行的任務(wù)總有結(jié)束的時候,那么dispatch_group_wait之后執(zhí)行的代碼就效果就與上一篇中的dispatch_group_notify的功能類似啦。
示例:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 5; i++) {
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:i];
NSLog(@"并發(fā)%d結(jié)束----線程:%@", i,[NSThread currentThread]);
} );
}
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result) {
NSLog(@"超時了");
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"執(zhí)行完畢,回主線程更新UI");
});
}
});
6.Dispatch_barrier_async和Dispatch_barrier_sync
這個函數(shù)的意思是:前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完畢之后才能執(zhí)行.
該函數(shù)同
dispatch_queue_creat函數(shù)生成的Concurrent Dispatch Queue一起使用.
注意:
1.從Xcode 4開始,我們定義property后,編譯器會自動幫我們添加@synthesize,但是如果我們同時重寫setter和getter,那么編譯器便不再幫我們添加@synthesize,我們需要自己添加@synthesize。
2.dispatch_barrier_async只能使用dispatch_queue_create創(chuàng)建的并發(fā)隊(duì)列,才能正確發(fā)揮它的作用。
dispatch_barrier_sync與dispatch_barrier_async的功能基本一致,不同之處是,dispatch_barrier_sync是在當(dāng)前線程中執(zhí)行block中的任務(wù),而dispatch_barrier_async則是在新的線程(有可能是之前使用過的子線程)中執(zhí)行任務(wù)。 它們都是在用dispatch_queue_create創(chuàng)建的并發(fā)隊(duì)列上有效果,而在串行隊(duì)列或者dispatch_get_global_queue創(chuàng)建的并發(fā)隊(duì)列中,作用與dispatch_sync一致。dispatch_barrier決定的只是它的任務(wù)是否在新的線程中執(zhí)行,以及它一定在前面幾個任務(wù)執(zhí)行完后執(zhí)行,并不會影響之前任務(wù)的執(zhí)行順序等。在串行隊(duì)列或者dispatch_get_global_queue創(chuàng)建的并發(fā)隊(duì)列中,dispatch_barrier_sync僅僅相當(dāng)于dispatch_sync
dispatch_barrier_async函數(shù)會等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束之后,再將指定的處理追加到該Concurrent Dispatch Queue中.然后在由dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后,Concurrent Dispatch Queue才恢復(fù)一般的動作,追加到該Concurrent Dispatch Queue的處理又開始并行執(zhí)行.
下面是你何時會——和不會——使用障礙函數(shù)的情況:
- 自定義串行隊(duì)列:一個很壞的選擇;障礙不會有任何幫助,因?yàn)椴还茉鯓?,一個串行隊(duì)列一次都只執(zhí)行一個操作。
- 全局并發(fā)隊(duì)列:要小心;這可能不是最好的主意,因?yàn)槠渌到y(tǒng)可能在使用隊(duì)列而且你不能壟斷它們只為你自己的目的。
- 自定義并發(fā)隊(duì)列:這對于原子或臨界區(qū)代碼來說是極佳的選擇。任何你在設(shè)置或?qū)嵗男枰€程安全的事物都是使用障礙的最佳候選。
由于上面唯一像樣的選擇是自定義并發(fā)隊(duì)列,你將創(chuàng)建一個你自己的隊(duì)列去處理你的障礙函數(shù)并分開讀和寫函數(shù)。且這個并發(fā)隊(duì)列將允許多個多操作同時進(jìn)行。
7.Dispatch_sync同步函數(shù)和Dispatch_async異步函數(shù)
dispatch_sync添加任務(wù)到一個隊(duì)列并等待直到任務(wù)完成。dispatch_async做類似的事情,但不同之處是它不會等待任務(wù)的完成,而是立即繼續(xù)“調(diào)用線程”的其它任務(wù)。
特別注意:在同步函數(shù)執(zhí)行主線程隊(duì)列的任務(wù)會發(fā)生死鎖,特別注意同步函數(shù)的死鎖情況
下面是一個快速總覽,關(guān)于在何時以及何處使用 dispatch_sync :
- 自定義串行隊(duì)列:在這個狀況下要非常小心!如果你正運(yùn)行在一個隊(duì)列并調(diào)用dispatch_sync 放在同一個隊(duì)列,那你就百分百地創(chuàng)建了一個死鎖。
- 主隊(duì)列(串行):同上面的理由一樣,必須非常小心!這個狀況同樣有潛在的導(dǎo)致死鎖的情況。
- 并發(fā)隊(duì)列:這才是做同步工作的好選擇,不論是通過調(diào)度障礙,或者需要等待一個任務(wù)完成才能執(zhí)行進(jìn)一步處理的情況。
下面是一個關(guān)于在 dispatch_async 上如何以及何時使用不同的隊(duì)列類型的快速指導(dǎo):
- 自定義串行隊(duì)列:當(dāng)你想串行執(zhí)行后臺任務(wù)并追蹤它時就是一個好選擇。這消除了資源爭用,因?yàn)槟阒酪淮沃挥幸粋€任務(wù)在執(zhí)行。注意若你需要來自某個方法的數(shù)據(jù),你必須內(nèi)聯(lián)另一個 Block 來找回它或考慮使用 dispatch_sync。
- 主隊(duì)列(串行):這是在一個并發(fā)隊(duì)列上完成任務(wù)后更新 UI 的共同選擇。要這樣做,你將在一個 Block 內(nèi)部編寫另一個 Block 。以及,如果你在主隊(duì)列調(diào)用 dispatch_async 到主隊(duì)列,你能確保這個新任務(wù)將在當(dāng)前方法完成后的某個時間執(zhí)行。
- 并發(fā)隊(duì)列:這是在后臺執(zhí)行非 UI 工作的共同選擇。
| 函數(shù)\隊(duì)列 | 并發(fā)隊(duì)列 | 自己創(chuàng)建的串行隊(duì)列 | 主隊(duì)列 |
| 同步 | 不會創(chuàng)建新線程,串行執(zhí)行 | 不會創(chuàng)建新線程,串行執(zhí)行 |不會創(chuàng)建新線程,串行執(zhí)行(可能會發(fā)生死鎖,如果最外層函數(shù)也是在主線程執(zhí)行)|
| 異步 | 會創(chuàng)建多條新線程,同時執(zhí)行多個任務(wù) | 會創(chuàng)建新線程,串行執(zhí)行任務(wù) |不會創(chuàng)建新線程,串行執(zhí)行|
8.Dispatch_apply
dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關(guān)聯(lián)API.該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束.
dispatch_apply 表現(xiàn)得就像一個 for 循環(huán),但它能并發(fā)地執(zhí)行不同的迭代。這個函數(shù)是同步的,所以和普通的 for 循環(huán)一樣,它只會在所有工作都完成后才會返回。
那何時才適合用 dispatch_apply 呢?
- 自定義串行隊(duì)列:串行隊(duì)列會完全抵消 dispatch_apply 的功能;你還不如直接使用普通的 for 循環(huán)。
- 主隊(duì)列(串行):與上面一樣,在串行隊(duì)列上不適合使用 dispatch_apply 。還是用普通的 for 循環(huán)吧。
- 并發(fā)隊(duì)列:對于并發(fā)循環(huán)來說是很好選擇,特別是當(dāng)你需要追蹤任務(wù)的進(jìn)度時。
該方法會等apply 中多次迭代調(diào)用的block全都執(zhí)行完成后,才會返回,所以dispatch_apply會阻塞當(dāng)前線程,我們得避免在主線程中使用dispatch_apply。另外,說明中已經(jīng)說的很清楚了,如果我們使用dispatch_get_global_queue創(chuàng)建的串行隊(duì)列,那么傳入的block任務(wù)是并發(fā)執(zhí)行的。如果我們在串行隊(duì)列中執(zhí)行該方法,會發(fā)生死鎖,所以第二個參數(shù),千萬不要傳串行隊(duì)列。如果我們使用dispatch_queue_create創(chuàng)建的并發(fā)隊(duì)列,block任務(wù)依然是順序執(zhí)行的。
下面看一下示例代碼以及運(yùn)行結(jié)果:dispatch_apply / dispatch_queue_create /并發(fā)隊(duì)列:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
dispatch_apply(5, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:index];
NSLog(@"并發(fā)%zu---%@",index,[NSThread currentThread]);
});
NSLog(@"done - %@",[NSThread currentThread]);
});
NSLog(@"主線程");
// 輸出結(jié)果:
2016-07-12 16:09:33.856 PractiseProject[5496:207665] 主線程
2016-07-12 16:09:33.857 PractiseProject[5496:207710] 并發(fā)0---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:34.860 PractiseProject[5496:207710] 并發(fā)1---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:36.864 PractiseProject[5496:207710] 并發(fā)2---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:39.867 PractiseProject[5496:207710] 并發(fā)3---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.872 PractiseProject[5496:207710] 并發(fā)4---<NSThread: 0x7f950053a920>{number = 3, name = (null)}
2016-07-12 16:09:43.873 PractiseProject[5496:207710] done - <NSThread: 0x7f950053a920>{number = 3, name = (null)}
dispatch_apply / dispatch_get_global_queue:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_apply(5, queue, ^(size_t index) {
[NSThread sleepForTimeInterval:index];
NSLog(@"并發(fā)%zu---%@",index,[NSThread currentThread]);
});
NSLog(@"done - %@",[NSThread currentThread]);
});
NSLog(@"主線程");
// 輸出結(jié)果:
2016-07-12 16:15:26.634 PractiseProject[5544:210845] 主線程
2016-07-12 16:15:26.634 PractiseProject[5544:210882] 并發(fā)0---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:27.637 PractiseProject[5544:210887] 并發(fā)1---<NSThread: 0x7f8731517e10>{number = 2, name = (null)}
2016-07-12 16:15:28.637 PractiseProject[5544:210893] 并發(fā)2---<NSThread: 0x7f8731412d50>{number = 4, name = (null)}
2016-07-12 16:15:29.636 PractiseProject[5544:210899] 并發(fā)3---<NSThread: 0x7f8731448cc0>{number = 5, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210882] 并發(fā)4---<NSThread: 0x7f8733816bc0>{number = 3, name = (null)}
2016-07-12 16:15:30.635 PractiseProject[5544:210893] done - <NSThread: 0x7f8731412d50>{number = 4, name = (null)}
9.Dispatch_suspend(掛起)/Dispatch_resume(恢復(fù))
10.Dispatch Semaphore
dispatch_semaphore_t信號量是一種老式的線程概念,由非常謙卑的 Edsger W. Dijkstra 介紹給世界。信號量之所以比較復(fù)雜是因?yàn)樗⒃诓僮飨到y(tǒng)的復(fù)雜性之上。
- (void)downloadImageURLWithString:(NSString *)URLString
{
// 1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURL *url = [NSURL URLWithString:URLString];
__unused Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *error) {
if (error) {
XCTFail(@"%@ failed. %@", URLString, error);
}
// 2
dispatch_semaphore_signal(semaphore);
}];
// 3
dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds);
if (dispatch_semaphore_wait(semaphore, timeoutTime)) {
XCTFail(@"%@ timed out", URLString);
}
}
在我們使用多線程處理多個并發(fā)任務(wù),而這多個并發(fā)任務(wù)有資源競爭的時候,就需要一種機(jī)制,在資源不夠用時,讓新的任務(wù)處于等待狀態(tài),當(dāng)有可用資源時,等待的任務(wù)在按序依次執(zhí)行。
像這一類問題除了可以用NSOperation,設(shè)置最大并發(fā)數(shù)外,還可以使用信號量。
這里涉及到的API有如下幾個:
dispatch_semaphore_t dispatch_semaphore_create(long value);
創(chuàng)建信號量的方法,如果初始值小于0,則會返回NULL,即信號量創(chuàng)建失敗。參數(shù):value 表示初始的信號量個數(shù),可以理解為資源個數(shù),或者最大并發(fā)個數(shù)。
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
參數(shù):第一個參數(shù)為信號量對象,第二個參數(shù)是等待超時的時間。
講解:該方法相當(dāng)于任務(wù)開始前的檢查,需要注意的是該方法會阻塞當(dāng)前線程。如果此時信號量的值大于0,會返回0,并且代碼會繼續(xù)往下執(zhí)行;如果此時信號量的值等于0,那么此時該方法會阻塞當(dāng)前線程,等待timeout 的時間。如果在超時的時間內(nèi),依然沒有可用的資源,那么該方法會返回一個非0的值。
該方法執(zhí)行時,會使信號量的值減1。
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
該方法應(yīng)該在任務(wù)執(zhí)行完畢時調(diào)用,它會使信號量的值加0。
下面用一段實(shí)際代碼演示GCD信號量的使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 在子線程中執(zhí)行,防止阻塞主線程
dispatch_async(queue, ^{
// 創(chuàng)建一個有3個資源的信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
for (int i = 0; i < 6; i++) {
// 檢測還有多少個資源,執(zhí)行后會使資源數(shù)減少1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"開始執(zhí)行任務(wù)%d---%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:6 - i];
NSLog(@"完成任務(wù)%d---%@",i,[NSThread currentThread]);
//表示資源使用完畢,會使資源數(shù)加1
dispatch_semaphore_signal(semaphore);
});
}
});
// 輸出結(jié)果:
2016-07-13 17:23:53.178 PractiseProject[4973:196435] 開始執(zhí)行任務(wù)1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196436] 開始執(zhí)行任務(wù)0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:53.178 PractiseProject[4973:196437] 開始執(zhí)行任務(wù)2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 完成任務(wù)2---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:57.179 PractiseProject[4973:196437] 開始執(zhí)行任務(wù)3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 完成任務(wù)1---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:58.182 PractiseProject[4973:196435] 開始執(zhí)行任務(wù)4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 完成任務(wù)0---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:23:59.179 PractiseProject[4973:196436] 開始執(zhí)行任務(wù)5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196435] 完成任務(wù)4---<NSThread: 0x7fc07060a090>{number = 4, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196437] 完成任務(wù)3---<NSThread: 0x7fc070455f10>{number = 5, name = (null)}
2016-07-13 17:24:00.184 PractiseProject[4973:196436] 完成任務(wù)5---<NSThread: 0x7fc070722030>{number = 3, name = (null)}
11.Dispatch_once
dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次指定處理的API.
以線程安全的方式執(zhí)行且僅執(zhí)行其代碼塊一次。試圖訪問臨界區(qū)(即傳遞給 dispatch_once 的代碼)的不同的線程會在臨界區(qū)已有一個線程的情況下被阻塞,直到臨界區(qū)完成為止。
12.Dispatch I/O
在讀取大文件時,如果將文件分成合適的大小并使用Global Dispatch Queue并列讀取的話,就是使用
dispatch I/O和Dispatch Data
13.Dispatch Source
GCD 的一個特別有趣的特性是 Dispatch Source,它基本上就是一個低級函數(shù)的 grab-bag ,能幫助你去響應(yīng)或監(jiān)測 Unix 信號、文件描述符、Mach 端口、VFS 節(jié)點(diǎn),以及其它晦澀的東西。所有這些都超出了本教程討論的范圍,但你可以通過實(shí)現(xiàn)一個 Dispatch Source 對象并以一個相當(dāng)奇特的方式來使用它來品嘗那些晦澀的東西。
有點(diǎn)不知道干嘛的,沒用過,可以看一下例子
GCD 深入理解:第二部分
14.Dispatch_source中的timer
dispatch_source_t 的類型有很多種:
#define DISPATCH_SOURCE_TYPE_DATA_ADD
#define DISPATCH_SOURCE_TYPE_DATA_OR
#define DISPATCH_SOURCE_TYPE_MACH_RECV
#define DISPATCH_SOURCE_TYPE_MACH_SEND
#define DISPATCH_SOURCE_TYPE_PROC
#define DISPATCH_SOURCE_TYPE_READ
#define DISPATCH_SOURCE_TYPE_SIGNAL
#define DISPATCH_SOURCE_TYPE_TIMER
#define DISPATCH_SOURCE_TYPE_VNODE
#define DISPATCH_SOURCE_TYPE_WRITE
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
這里記錄的是dispatch_source 中定時器timer的用法。
這里我就用dispatch_source 封裝一個timer 的方法,可以在傳入兩個block,分別在循環(huán)執(zhí)行,結(jié)束時執(zhí)行。當(dāng)然咯,下面這個方法還可以再加一個間隔時間參數(shù)。
- (void)startTimerWithTimeout:(NSTimeInterval)timeout eventBlock:(void (^)())eventBlock endBlock:(void (^)())endBlock
{
__block NSTimeInterval tempTimeout = timeout;
// 創(chuàng)建一個隊(duì)列,不管你創(chuàng)建什么類型的隊(duì)列,最終event_handler 都是在子線程中執(zhí)行的
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 創(chuàng)建一個計(jì)時器類型的源
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置定時器參數(shù),定時器開始的時間、每隔多久執(zhí)行一次、精度(可以延遲的納秒數(shù),最高精度是0,實(shí)際還是會有偏差,感覺沒什么卵用)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"EVET ---- %@",[NSThread currentThread]);
if (tempTimeout <= 0) {
// 倒計(jì)時結(jié)束,取消源
dispatch_source_cancel(timer);
// 回到主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 倒計(jì)時結(jié)束時界面的UI的更新
if (endBlock) {
endBlock();
}
});
} else {
// 回到主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 做界面的UI的更新
if (eventBlock) {
eventBlock();
}
});
tempTimeout--;
}
});
dispatch_resume(timer);
}
封裝好之后,調(diào)用起來就非常的Easy啦,并且看起來也挺舒服的。其實(shí)你可以把上面的方法封裝成一個工具類方法。
[self startTimerWithTimeout:30 eventBlock:^{
NSLog(@"定時執(zhí)行---%@",[NSThread currentThread]);
} endBlock:^{
NSLog(@"結(jié)束執(zhí)行---%@",[NSThread currentThread]);
}];
15.Dispatch_source 中神奇的數(shù)據(jù)合并
上面介紹了dispatch_source 有多種類型,發(fā)現(xiàn)一種神奇的類型DISPATCH_SOURCE_TYPE_DATA_ADD,這種類型的source 有什么特別之處呢?假如我們并發(fā)執(zhí)行多個任務(wù),這種類型的source 會在任務(wù)完成時,將data 加1,然后如果主線程比較空閑,那么event_handler就會多次調(diào)用,而如果主線程恰好比較忙碌,那么就會將任務(wù)合并,event_handler調(diào)用次數(shù)就會比較少。
還是先上一個代碼范例:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
dispatch_source_set_event_handler(source, ^{
unsigned long completion = dispatch_source_get_data(source);
NSLog(@"完成的任務(wù)個數(shù):%lu----%@",completion,[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
});
dispatch_resume(source);
dispatch_async(queue, ^{
NSLog(@"網(wǎng)絡(luò)線程---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_source_merge_data(source, 1);
});
dispatch_async(queue, ^{
NSLog(@"網(wǎng)絡(luò)線程---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_source_merge_data(source, 1);
});
//打印結(jié)果:
2016-07-13 15:38:45.911 PractiseProject[4130:150701] 網(wǎng)絡(luò)線程---<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:45.911 PractiseProject[4130:150694] 網(wǎng)絡(luò)線程---<NSThread: 0x7f9e12da6cb0>{number = 3, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150701] 完成的任務(wù)個數(shù):2----<NSThread: 0x7f9e12c10c90>{number = 2, name = (null)}
2016-07-13 15:38:47.915 PractiseProject[4130:150659] 更新UI
// 這也是打印結(jié)果:
2016-07-13 15:48:56.601 PractiseProject[4212:155405] 網(wǎng)絡(luò)線程---<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:56.601 PractiseProject[4212:155411] 網(wǎng)絡(luò)線程---<NSThread: 0x7fe390e0f800>{number = 3, name = (null)}
2016-07-13 15:48:59.304 PractiseProject[4212:155405] 完成的任務(wù)個數(shù):1----<NSThread: 0x7fe390e09270>{number = 2, name = (null)}
2016-07-13 15:48:59.330 PractiseProject[4212:155377] 更新UI
2016-07-13 15:49:01.309 PractiseProject[4212:155415] 完成的任務(wù)個數(shù):1----<NSThread: 0x7fe390e1d630>{number = 4, name = (null)}
2016-07-13 15:49:01.309 PractiseProject[4212:155377] 更新UI
它會根據(jù)主線程的繁忙與空閑,以及每個任務(wù)完成時的時間,減少返回次數(shù)或者每次返回。使用場景主要可以用在同時執(zhí)行多個任務(wù),任務(wù)的完成個數(shù)這種情況。
16.Queue-Specific
由于dispatch_get_current_queueAPI的移除,為了能夠判斷當(dāng)前queue是否是之前創(chuàng)建的queue,我們可以利用dispatch_queue_set_specific和dispatch_get_specific給queue關(guān)聯(lián)一個context data,后面再利用這個標(biāo)識獲取到context data。如果可以獲取到說明當(dāng)前上下文是在自己創(chuàng)建的queue中,如果不能獲取到context data則表示當(dāng)前是在其他隊(duì)列上。使用場景: 自己創(chuàng)建一個隊(duì)列,然后保證所有的操作都在該隊(duì)列上執(zhí)行。XMPP中有比較多的dispatch_queue_set_specific和dispatch_get_specific使用案例。
設(shè)置標(biāo)識和關(guān)聯(lián)的數(shù)據(jù):
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);
const void *queueSpecificKey = @"queueSpecificKey";
dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey, NULL);
獲取關(guān)聯(lián)數(shù)據(jù):
dispatch_get_specific(queueSpecificKey)
完整的示例:
dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_SERIAL);
// 當(dāng)然這里也可以是其他類型的隊(duì)列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// dispatch_queue_t queue = dispatch_queue_create("com.haley.cn", DISPATCH_QUEUE_CONCURRENT);
const void *queueSpecificKey = @"queueSpecificKey";
dispatch_queue_set_specific(queue, queueSpecificKey, &queueSpecificKey, NULL);
dispatch_async(queue, ^{
NSLog(@"異步任務(wù)");
if (dispatch_get_specific(queueSpecificKey)) {
NSLog(@"com.haley.cn---1隊(duì)列");
} else {
NSLog(@"---1其他隊(duì)列");
}
});
NSLog(@"主線程,主隊(duì)列");
if (dispatch_get_specific(queueSpecificKey)) {
NSLog(@"com.haley.cn---2隊(duì)列");
} else {
NSLog(@"----2其他隊(duì)列");
}
// 打印結(jié)果:
2016-07-11 14:30:56.772 PractiseProject[3379:152363] 主線程,主隊(duì)列
2016-07-11 14:30:56.772 PractiseProject[3379:152363] ----2其他隊(duì)列
2016-07-11 14:30:56.772 PractiseProject[3379:152451] 異步任務(wù)
2016-07-11 14:30:56.773 PractiseProject[3379:152451] com.haley.cn---1隊(duì)列
dispatch_get_specific所處的環(huán)境如果是在目標(biāo)對列上時,就可以獲取到關(guān)聯(lián)的數(shù)據(jù),否則就無法獲取關(guān)聯(lián)數(shù)據(jù),返回NULL??匆豢碭MPP中的使用案例:
- (BOOL)activate:(XMPPStream *)aXmppStream{
__block BOOL result = YES;
dispatch_block_t block = ^{
if (xmppStream != nil) {
result = NO;
} else {
xmppStream = aXmppStream;
[xmppStream addDelegate:self delegateQueue:moduleQueue];
[xmppStream registerModule:self];
} };
if (dispatch_get_specific(moduleQueueTag)){
block();
} else {
dispatch_sync(moduleQueue, block);
return result;
}
為了保證block是在目標(biāo)隊(duì)列上執(zhí)行,先判斷當(dāng)前是否在目標(biāo)隊(duì)列上(如果能取到關(guān)聯(lián)數(shù)據(jù),則說明在當(dāng)前隊(duì)列上),如果在目標(biāo)隊(duì)列上,直接執(zhí)行block,否則就在目標(biāo)隊(duì)列上同步執(zhí)行。
注意死鎖的情況
情形一:在主線程中調(diào)度主隊(duì)列完成一個同步任務(wù),會發(fā)生死鎖。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"串行----線程:%@",[NSThread currentThread]);
});
}
如上代碼,界面永遠(yuǎn)不會加載出來,里面的NSLog永遠(yuǎn)也不會執(zhí)行。原因是ViewDidLoad是在主隊(duì)列的主線程中執(zhí)行,執(zhí)行到dispatch_sync 時會阻塞住,等待dispatch_sync中的打印任務(wù)執(zhí)行完畢。而dispatch_sync又會等viewDidLoad執(zhí)行完畢,再開始執(zhí)行,因此就互相等待發(fā)生死鎖。
情形二:在串行隊(duì)列的同步任務(wù)中再執(zhí)行同步任務(wù),會發(fā)生死鎖。
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serial_queue, ^{
NSLog(@"串行1----線程:%@",[NSThread currentThread]);
dispatch_sync(serial_queue, ^{
NSLog(@"串行2----線程:%@",[NSThread currentThread]);
});
});
上面示例中的NSLog(@"串行1----線程:%@",[NSThread currentThread]);會打印.
但是NSLog(@"串行2----線程:%@",[NSThread currentThread]);永遠(yuǎn)也不會執(zhí)行。
因?yàn)榇嘘?duì)列一次只能執(zhí)行一個任務(wù),執(zhí)行完畢返回后,才會執(zhí)行下一個任務(wù),而外層任務(wù)的完成需要等待內(nèi)層任務(wù)的結(jié)束,而內(nèi)層任務(wù)的開始需要等外層任務(wù)結(jié)束。
其實(shí)情形一是情形二的一種特殊情況。
情形三:在串行隊(duì)列的異步任務(wù)中再嵌套執(zhí)行同步任務(wù),也會發(fā)生死鎖。
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue, ^{
NSLog(@"串行異步----線程:%@",[NSThread currentThread]);
dispatch_sync(serial_queue, ^{
NSLog(@"串行2----線程:%@",[NSThread currentThread]);
});
[NSThread sleepForTimeInterval:2.0];
});
同樣的,由于串行隊(duì)列一次只能執(zhí)行一個任務(wù),任務(wù)結(jié)束后,才能執(zhí)行下一個任務(wù)。
所以異步任務(wù)的結(jié)束需要等里面同步任務(wù)結(jié)束,而里面同步任務(wù)的開始需要等外面異步任務(wù)結(jié)束,所以就相互等待,發(fā)生死鎖了。