認(rèn)識Grand Central Dispatch (GCD)

首先先引用陽神Sunny博客中的幾道面試題:

Snip20160728_1.png

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,如下圖:

Snip20160731_1.png

該方法通過設(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针贬。

Snip20160731_2.png
  • 2.dispatch_set_target_queue方法設(shè)置優(yōu)先級
Snip20160731_3.png

第一個(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

Snip20160731_4.png

如果我們需要把不同隊(duì)列中得不同任務(wù)按照順序去執(zhí)行瞬铸,例如圖中的queue1queue2分別存放兩個(gè)任務(wù),此時(shí)要求輸出必須為2134础锐,所以調(diào)用dispatch_set_target_queue方法讓queue1queue2分別指定目標(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í)行方式的組合

所有組合及情況見下圖:


Snip20160731_5.png

注意第一種不能用的情況是當(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)致死鎖,為什么瓦哎?


Snip20160731_6.png

主線程是串行的砸喻,在執(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)致死鎖也切。

Snip20160803_1.png

不過上面僅僅是為了舉例扑媚,實(shí)際中并沒有用過,一個(gè)比較好的例子就是FMDB中就用了此方法防止死鎖的雷恃。

Snip20160803_4.png

Snip20160803_6.png

注意:如果不在隊(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í)行。

Snip20160805_3.png

dispatch_barrier_sync也可以實(shí)現(xiàn)上述功能

Snip20160805_1.png

不過我們發(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_suspenddispatch_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挂捅,下次再見芹助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闲先,隨后出現(xiàn)的幾起案子状土,更是在濱河造成了極大的恐慌,老刑警劉巖伺糠,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙谓,死亡現(xiàn)場離奇詭異,居然都是意外死亡训桶,警方通過查閱死者的電腦和手機(jī)累驮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門酣倾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谤专,你說我怎么就攤上這事躁锡。” “怎么了置侍?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵映之,是天一觀的道長。 經(jīng)常有香客問我蜡坊,道長杠输,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任秕衙,我火速辦了婚禮蠢甲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灾梦。我一直安慰自己峡钓,他們只是感情好妓笙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布若河。 她就那樣靜靜地躺著,像睡著了一般寞宫。 火紅的嫁衣襯著肌膚如雪萧福。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天辈赋,我揣著相機(jī)與錄音鲫忍,去河邊找鬼。 笑死钥屈,一個(gè)胖子當(dāng)著我的面吹牛悟民,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播篷就,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼射亏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了竭业?” 一聲冷哼從身側(cè)響起智润,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎未辆,沒想到半個(gè)月后窟绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咐柜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年兼蜈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攘残。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饭尝,死狀恐怖肯腕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钥平,我是刑警寧澤实撒,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站涉瘾,受9級特大地震影響知态,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜立叛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一负敏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秘蛇,春花似錦其做、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至艘策,卻和暖如春蹈胡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背朋蔫。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工罚渐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驯妄。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓荷并,卻偏偏與公主長得像,于是被迫代替她去往敵國和親青扔。 傳聞我的和親對象是個(gè)殘疾皇子源织,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Managing Units of Work(管理工作單位) 調(diào)度塊允許您直接配置隊(duì)列中各個(gè)工作單元的屬性。它們還...
    edison0428閱讀 7,992評論 0 1
  • 最近頗花了一番功夫把多線程GCD人的一些用法總結(jié)出來赎懦,一來幫自己鞏固一下知識雀鹃、二來希望能幫到對這一塊還迷茫...
    人活一世閱讀 290評論 1 1
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時(shí)候我都想好了最壞的可能(胃癌)励两,之前在網(wǎng)上查的癥狀都很相似黎茎。...
    Dely閱讀 9,243評論 21 42
  • 第二十一天 人說21天能養(yǎng)成一個(gè)習(xí)慣傅瞻,今天正好21天踢代。 可是失眠了,睡眠不足的情況下嗅骄,明天怎么早起胳挎? 此刻把文字補(bǔ)...
    云小5閱讀 410評論 0 2
  • 1.【太7:6】不要把圣物給狗,也不要把你們的珍珠丟在豬前溺森,恐怕它踐踏了珍珠慕爬,轉(zhuǎn)過來咬你們。 ??? 2.不要論斷...
    胡涂格格閱讀 1,544評論 0 0