首先先引用陽神Sunny博客中的幾道面試題:
GCD開發(fā)中用的十分廣泛杈绸,所以有必要進(jìn)行深入的了解帖蔓。下面就一步一步的深入下去。
概述
說到GCD自然就會(huì)想到多線程瞳脓,GCD是一種異步執(zhí)行任務(wù)的技術(shù)塑娇,它避免了讓程序員直接操作線程的種種麻煩。在GCD中開發(fā)者只需要定義一系列的任務(wù)放到合適的運(yùn)行隊(duì)列中執(zhí)行即可劫侧,這樣GCD就會(huì)根據(jù)情況開若干條線程同時(shí)負(fù)責(zé)線程的生命周期。
隊(duì)列
GCD只有兩種類型的隊(duì)列:
DISPATCH_QUEUE_SERIAL
串行隊(duì)列
DISPATCH_QUEUE_CONCURRENT
并行隊(duì)列
- 兩種隊(duì)列都是
dispatch_queue_t
類型的對象。
可以通過如下方法創(chuàng)建(第一個(gè)參數(shù)用來標(biāo)識隊(duì)列方便調(diào)試時(shí)候查看)奇瘦。
dispatch_queue_create("com.longjianjiang.queue", DISPATCH_QUEUE_CONCURRENT);
- 兩種隊(duì)列的執(zhí)行方式都是按照先進(jìn)先出的原則,只是串行隊(duì)列一次只執(zhí)行一個(gè)任務(wù)劲弦,而并行隊(duì)列在資源允許的情況下會(huì)開線程一次執(zhí)行多個(gè)任務(wù)耳标。
兩個(gè)特殊的隊(duì)列
-
dispatch_get_global_queue(long identifier, unsigned long flags)
系統(tǒng)提供的全局并行隊(duì)列,可以指定優(yōu)先級邑跪,一般默認(rèn)選擇DISPATCH_QUEUE_PRIORITY_DEFAULT
. -
dispatch_get_main_queue()
系統(tǒng)提供的主隊(duì)列(串行隊(duì)列)次坡,也就是提交的任務(wù)會(huì)在主線程執(zhí)行.一般更新UI相關(guān)會(huì)用到主隊(duì)列。
隊(duì)列優(yōu)先級
默認(rèn)我們通過dispatch_queue_create
方法創(chuàng)建的隊(duì)列優(yōu)先級默認(rèn)是DISPATCH_QUEUE_PRIORITY_DEFAULT
画畅。如果想設(shè)置隊(duì)列的優(yōu)先級有兩種方法砸琅。
- 1.
dispatch_queue_attr_make_with_qos_class
,如下圖:
該方法通過設(shè)置
dispatch_queue_attr_t
來設(shè)置隊(duì)列的優(yōu)先級轴踱。
第一個(gè)參數(shù)
dispatch_queue_attr_t attr
:與特定的服務(wù)質(zhì)量類相關(guān)聯(lián)的隊(duì)列的屬性值信息症脂。如果你想讓被提交的任務(wù)被連續(xù)的執(zhí)行,則指定DISPATCH_QUEUE_SERIAL值淫僻,或如果你想讓被提交的任務(wù)被并發(fā)的執(zhí)行诱篷,則指定DISPATCH_QUEUE_CONCURRENT值。如果你傳NULL雳灵,則此方法默認(rèn)創(chuàng)建一個(gè)連續(xù)的隊(duì)列棕所。
第二個(gè)參數(shù)dispatch_qos_class_t os_class
:和隊(duì)列優(yōu)先級dispatch_queue_priority_t
類似,同樣有四種悯辙,具體和隊(duì)列優(yōu)先級的映射見下圖琳省。
第三個(gè)參數(shù)int relative_priority
:對第二個(gè)參數(shù)四個(gè)特定的服務(wù)質(zhì)量優(yōu)先級所代表的值的一個(gè)偏移,這個(gè)值必須不大于于0并且不小于QOS_MIN_RELATIVE_PRIORITY
躲撰,否則返回為NULL
.一般默認(rèn)為0针贬。
- 2.
dispatch_set_target_queue
方法設(shè)置優(yōu)先級
第一個(gè)參數(shù)
dispatch_object_t object
: 要修改的隊(duì)列,這個(gè)參數(shù)不能為NULL
拢蛋。
第二個(gè)參數(shù)dispatch_queue_t queue
:有優(yōu)先級的隊(duì)列坚踩,執(zhí)行完方法,前一個(gè)沒有優(yōu)先級的隊(duì)列優(yōu)先級和此隊(duì)列相同瓤狐。
改變多個(gè)隊(duì)列任務(wù)的執(zhí)行順序
dispatch_set_target_queue
如果我們需要把不同隊(duì)列中得不同任務(wù)按照順序去執(zhí)行瞬铸,例如圖中的
queue1
和queue2
分別存放兩個(gè)任務(wù),此時(shí)要求輸出必須為2134础锐,所以調(diào)用dispatch_set_target_queue
方法讓queue1
和queue2
分別指定目標(biāo)為串行隊(duì)列consultQueue
嗓节,此時(shí)原本應(yīng)該并行執(zhí)行的四個(gè)任務(wù)只能一個(gè)一個(gè)依次執(zhí)行。
執(zhí)行方式
GCD只有兩種執(zhí)行方式
dispatch_sync
同步執(zhí)行
dispatch_async
異步執(zhí)行
- 同步執(zhí)行就是多個(gè)任務(wù)依次按順序執(zhí)行皆警,一個(gè)接著一個(gè)的執(zhí)行拦宣。
- 異步執(zhí)行就是在執(zhí)行某個(gè)任務(wù)的時(shí)候,不等任務(wù)結(jié)束就可以返回,其他任務(wù)依然可以繼續(xù)鸵隧,也就是說異步執(zhí)行通常會(huì)開新線程绸罗。
比如下載一張圖片顯示,要先從網(wǎng)絡(luò)上下載圖片豆瘫,然后更新UI珊蟀。同步方法就是等待圖片下載完成再更新UI,而異步則是立刻從圖片下載的方法返回并向后執(zhí)行外驱,此時(shí)我們依然可以處理界面上的點(diǎn)擊事件育灸,否則主線程就被阻塞了。
隊(duì)列和執(zhí)行方式的組合
所有組合及情況見下圖:
注意第一種不能用的情況是當(dāng)前線程在主線程昵宇,如果是非主線程的話則是可以的
// 該方法對當(dāng)前線程進(jìn)行判斷磅崭,從而避免的死鎖的發(fā)生
void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
死鎖問題
下圖會(huì)導(dǎo)致死鎖,為什么瓦哎?
主線程是串行的砸喻,在執(zhí)行某一個(gè)任務(wù)的時(shí)候線程被阻塞了,而這個(gè)任務(wù)(dispatch_sync)在執(zhí)行時(shí)蒋譬,又要求阻塞主線程恩够,從而導(dǎo)致了互相的阻塞,也就是死鎖羡铲。
避免死鎖
除了特定要求需要同步執(zhí)行蜂桶,那么我們沒有理由不充分利用CPU選擇異步執(zhí)行。
dispatch_queue_set_specific
,dispatch_queue_get_specific
,dispatch_get_specific
配合使用可以防止在串行隊(duì)列中的同步任務(wù)嵌套一個(gè)此隊(duì)列的同步任務(wù)從而導(dǎo)致死鎖也切。
不過上面僅僅是為了舉例扑媚,實(shí)際中并沒有用過,一個(gè)比較好的例子就是FMDB中就用了此方法防止死鎖的雷恃。
注意:如果不在隊(duì)列中想要通過key獲取到context疆股,得使用
dispatch_queue_get_specific
傳入?yún)?shù)隊(duì)列才能獲取。
dispatch_get_current_queue
此方法iOS6中被廢棄了倒槐,為什么呢旬痹?
首先如果隊(duì)列調(diào)用了dispatch_set_target_queue
方法
dispatch_set_target_queue(queue, targetQueue);
1.此時(shí)如果調(diào)用dispatch_get_current_queue
,是應(yīng)該返回queue
還是targetQueue
呢?
2.如下圖讨越,通過dispatch_get_current_queue
方法判斷當(dāng)前隊(duì)列是否為queueA
,如果不是就同步執(zhí)行一個(gè)任務(wù)两残。
if (queueA == dispatch_get_current_queue()){
block();
} else {
dispatch_sync(queueA,block);
}
例如同步執(zhí)行的block如下所示
dispatch_sync(queueB, ^{
//此時(shí)通過`dispatch_get_current_queue`得到的隊(duì)列是`queueB`
//但此時(shí)`queueA`是被阻塞的,
//所以繼續(xù)執(zhí)行下面任務(wù)就會(huì)死鎖把跨。
dispatch_sync(queueA, ^{
// some task
});
});
所以dispatch_set_target_queue
使用不當(dāng)會(huì)導(dǎo)致死鎖人弓,我們可以使用之前的dispatch_queue_get_specific
來實(shí)現(xiàn)相關(guān)功能。
附蘋果文檔的解釋:
Recommended for debugging and logging purposes only: The code must not make any assumptions about the queue returned, unless it is one of the global queues or a queue the code has itself created. The code must not assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue().
三種特殊的執(zhí)行
-
dispatch_once
一次執(zhí)行着逐,大多用來創(chuàng)建單例或者全局的數(shù)據(jù)崔赌。
+ (UIColor *)color {
static UIColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [UIColor orangeColor];
});
return color;
}
-
dispatch_after
延遲執(zhí)行意蛀,不過blcok中任務(wù)不可以取消,所以建議如果可以的話使用-viewWillAppear
健芭、-viewDidAppear
會(huì)更好县钥。
Snip20160803_8.png -
dispatch_apply
類似for循環(huán)的一個(gè)方法,按指定的次數(shù)將指定的block
追加到指定的隊(duì)列中,并等到全部的處理執(zhí)行結(jié)束,默認(rèn)同步執(zhí)行慈迈,所以傳入的隊(duì)列不能為主隊(duì)列若贮,否則會(huì)死鎖。但當(dāng)傳入的時(shí)全局隊(duì)列的時(shí)候吩翻,執(zhí)行是異步的 。 同時(shí)只有當(dāng)執(zhí)行完對應(yīng)的次數(shù)后才會(huì)執(zhí)行下面的代碼锥咸,所以最后才輸出 done狭瞎。
Snip20160803_10.png
dispatch groups
開發(fā)中我們的應(yīng)用通常會(huì)向服務(wù)器發(fā)送一連串的請求,比如說應(yīng)用啟動(dòng)的時(shí)候會(huì)向服務(wù)端請求一些配置信息搏予,這些配置信息可能需要多個(gè)請求組合而成熊锭,而且這些請求彼此之間并沒有關(guān)聯(lián),那么這個(gè)時(shí)候問題來了雪侥,我們?nèi)绾沃肋@些任務(wù)什么時(shí)候執(zhí)行完成了呢碗殷?
此時(shí)你就需要?jiǎng)?chuàng)建一個(gè)dispatch_group_t
:
dispatch_group_t group = dispatch_group_create();
下面我們可以將之前的任務(wù)添加到group
中:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
//需要執(zhí)行的任務(wù)
});
但是當(dāng)有些任務(wù)異步執(zhí)行,會(huì)馬上返回速缨,這個(gè)時(shí)候group
就會(huì)認(rèn)為放到group
中的任務(wù)已經(jīng)結(jié)束锌妻,顯然不合理。
這個(gè)時(shí)候我們可以通過dispatch_group_enter
表示要開始某個(gè)任務(wù)了旬牲,結(jié)束任務(wù)之后需要調(diào)用dispatch_group_leave
來退出group
仿粹。
dispatch_group_enter(group);
[service startWithCompletion:^(response *results, NSError* error){
// 需要執(zhí)行的任務(wù)
dispatch_group_leave(serviceGroup);
}];
最后告訴group
任務(wù)執(zhí)行完成
- 第一種方式:
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
第二個(gè)參數(shù)timeout
表示需要等待的時(shí)間,系統(tǒng)定義了兩個(gè)常用的值DISPATCH_TIME_NOW
原茅、 DISPATCH_TIME_FOREVER
吭历,
如果使用了第一個(gè)值,表示會(huì)立即查看是否完成任務(wù)擂橘,第二個(gè)表示會(huì)等待任務(wù)全部結(jié)束晌区。此時(shí)會(huì)阻塞當(dāng)前的線程,直到dispatch group中的所有任務(wù)完成才會(huì)返回.
返回值如果是0表示group
中的任務(wù)執(zhí)行結(jié)束,否則就不為0.
- 第二種方式
dispatch_group_notify(group, queue, ^{
//不會(huì)阻塞當(dāng)前線程
});
兩種方式按需求選擇即可通贞。
Using Barriers
在進(jìn)行文件讀和寫或者數(shù)據(jù)庫操作的時(shí)候朗若,我們必須保證寫數(shù)據(jù)的時(shí)候和修改數(shù)據(jù)庫的時(shí)候有且僅有一個(gè)線程在操作,此時(shí)GCD提供了一個(gè)好的方法避免寫沖突昌罩。
dispatch_barrier_async
用于等待前面的任務(wù)執(zhí)行完畢后自己才執(zhí)行捡偏,而它后面的任務(wù)需等待它完成之后才執(zhí)行。
dispatch_barrier_sync
也可以實(shí)現(xiàn)上述功能
不過我們發(fā)現(xiàn)輸出2222222222222的位置兩者不一樣峡迷,這是因?yàn)?code>dispatch_barrier_sync會(huì)阻塞當(dāng)前線程银伟,而dispatch_barrier_async
則不會(huì)你虹。
Dispatch Semaphore
信號量也是用來處理當(dāng)多個(gè)線程對某個(gè)資源更新可能產(chǎn)生數(shù)據(jù)的誤操作。
信號量是一個(gè)整形值并且具有一個(gè)初始計(jì)數(shù)值彤避,并且支持兩個(gè)操作:信號通知和等待傅物。當(dāng)一個(gè)信號量被信號通知,其計(jì)數(shù)會(huì)被增加琉预。當(dāng)一個(gè)線程在一個(gè)信號量上等待時(shí)董饰,計(jì)數(shù)會(huì)減少,當(dāng)信號量為0圆米,線程會(huì)被阻塞卒暂。
在GCD中有三個(gè)函數(shù)是semaphore的操作,分別是:
dispatch_semaphore_create
創(chuàng)建一個(gè)semaphore
dispatch_semaphore_signal
發(fā)送一個(gè)信號
dispatch_semaphore_wait
等待信號
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArrayarray array];
for (int index = 0; index < 100000; index++) {
dispatch_async(queue, ^(){
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
dispatch_semaphore_signal(semaphore);
});
}
注意:
-
dispatch_semaphore_wait
的第二個(gè)參數(shù)和之前的dispatch_group_wait
是一樣的娄帖。返回值如果是0也祠,說明此時(shí)信號量大于等于1,可以執(zhí)行任務(wù)近速,非0的話則說明已處于阻塞狀態(tài)诈嘿。 - 當(dāng)執(zhí)行完操作之后應(yīng)該調(diào)用
dispatch_semaphore_signal
方法,以便其他任務(wù)有機(jī)會(huì)去執(zhí)行削葱。
Dispatch 其他
-
dispatch_suspend
和dispatch_resume
dispatch_suspend
掛起指定的Dispatch Queue奖亚。
dispatch_resume
恢復(fù)指定的Dispatch Queue。
兩者對已經(jīng)執(zhí)行的處理沒有影響析砸。掛起后昔字,追加到Dispatch Queue中但尚未執(zhí)行的處理在此之后停止執(zhí)行。而恢復(fù)則使這些處理能夠繼續(xù)執(zhí)行首繁。 -
dispatch_main
該方法可以阻塞主線程李滴,同時(shí)必須只能在主線程中調(diào)用,否則會(huì)導(dǎo)致程序崩潰蛮瞄。
最后所坯,面試題的答案都有了!
尾巴
歡迎關(guān)注@longjianjiang挂捅,下次再見芹助。