2019-11-29

在我們做iOS開發(fā)的過程中税娜,經(jīng)常會與多線程打交道坐搔,異步繪制,網(wǎng)絡(luò)請求等敬矩,方式有NSThread概行,NSOperationQueue,GCD等弧岳,在這里GCD的地位舉足輕重凳忙,那么今天寫一篇關(guān)于GCD的文章。首先迎來第一個問題:

什么是GCD

全名叫 Grand Central Dispatch 是一種異步執(zhí)行任務(wù)的技術(shù)禽炬,一套基于c語言實現(xiàn)的api涧卵,語法十分簡潔,只需要簡單定義任務(wù)或按需加入到隊列中腹尖,就可以按照計劃實現(xiàn)功能柳恐,下面一個簡單的例子。

dispatch_async(dispatch_get_global_queue(0,0),^{//網(wǎng)絡(luò)請求//異步繪制圖像//數(shù)據(jù)庫訪問等dispatch_async(dispatch_get_main_queue(),^{//刷新UI});});

在這里使用異步的方式定義了一個任務(wù)热幔,并將其添加到一個全局隊列中胎撤,這里面的任務(wù)內(nèi)容可以進行一些耗時操作,由于是異步所以不影響主線程断凶,當(dāng)任務(wù)結(jié)束之后,同樣使用異步的方式定義了一個任務(wù)巫俺,將其添加到了主隊列中认烁,這里執(zhí)行的任務(wù)會在主線程完成。

同步和異步

前面內(nèi)容提到了“異步”一詞介汹,那么這里聊一下什么是“異步”却嗡,相應(yīng)的還有“同步”一詞。

異步的作用是不需要CPU立刻響應(yīng)嘹承,而是等待一個信號或回調(diào)來下一步操作窗价,當(dāng)前線程可以立刻執(zhí)行后續(xù)內(nèi)容,而同步為可以阻塞當(dāng)前線程叹卷,當(dāng)執(zhí)行完一個任務(wù)之后才能執(zhí)行下一個撼港。

異步是目的坪它,多線程是手段

當(dāng)我們開啟一個新的線程之后,可以將任務(wù)交由新的線程執(zhí)行帝牡,實現(xiàn)異步效果往毡,當(dāng)需要的時候再返回之前的線程。

用圖來做一個同步和異步的解釋

異步和同步

這里thread1模擬主線程靶溜,當(dāng)執(zhí)行任務(wù)A的時候由于耗時比較短开瞭,所以可以在主線程上完成,當(dāng)執(zhí)行到任務(wù)B的時候罩息,由于耗時長嗤详,所以開辟了一條新的線程,將任務(wù)交由子線程處理瓷炮,同時繼續(xù)完成任務(wù)C葱色,當(dāng)耗時操作完成之后,將結(jié)果返回給主線程進行操作崭别,實現(xiàn)了異步功能冬筒。

使用多線程仍然要注意幾個問題,1.數(shù)據(jù)競爭 2.死鎖 3.線程開辟太多導(dǎo)致內(nèi)存消耗過大茅主,不過只要使用得當(dāng)舞痰,多線程對我們開發(fā)的好處十分巨大。

串行隊列和并發(fā)隊列

首先解釋一下“隊列”诀姚,如其名字响牛,它是執(zhí)行處理的等待隊列,這里的隊列調(diào)度方式為先進先出模式(FIFO-First In First Out)赫段,也就是先添加到隊列中的任務(wù)先執(zhí)行呀打,當(dāng)然還有其他幾種模式,不過這里暫時不考慮糯笙,有興趣的請自行查資料

FIFO

隊列的種類也是兩種贬丛,Serial Dispatch Queue和 Concurrent Dispatch Queue,分別為串行隊列和并發(fā)隊列给涕,串行隊列中的任務(wù)必須按照順序依次執(zhí)行豺憔,并發(fā)隊列可以多個線程以并發(fā)的形式執(zhí)行。

串行隊列和并發(fā)隊列

下面用代碼來看串行隊列和并發(fā)隊列的區(qū)別够庙,首先使用串行隊列的方式創(chuàng)建幾個異步操作

dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_SERIAL);dispatch_async(queue,^{NSLog(@"%@---0",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---1",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---2",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---3",[NSThread currentThread]);});

看看輸出結(jié)果

<NSThread:0x6000002777c0>{number=3,name=(null)}---0<NSThread:0x6000002777c0>{number=3,name=(null)}---1<NSThread:0x6000002777c0>{number=3,name=(null)}---2<NSThread:0x6000002777c0>{number=3,name=(null)}---3

很明顯恭应,這里是同一個線程,而且也確實是按照順序執(zhí)行的耘眨,那么接下來使用并發(fā)隊列來操作昼榛。

dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue,^{NSLog(@"%@---0",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---1",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---2",[NSThread currentThread]);});dispatch_async(queue,^{NSLog(@"%@---3",[NSThread currentThread]);});

再看看輸出結(jié)果

<NSThread:0x60400046adc0>{number=5,name=(null)}---2<NSThread:0x60400046b000>{number=3,name=(null)}---0<NSThread:0x6000004655c0>{number=4,name=(null)}---1<NSThread:0x6000004653c0>{number=6,name=(null)}---3

很明顯,并沒有按照順序執(zhí)行剔难,而且不是同一個線程胆屿,這就印證了上面所說的串行隊列和并發(fā)隊列的區(qū)別奥喻。

但是不代表創(chuàng)建了多少個并發(fā)任務(wù)就會開辟多少個線程,這個線程數(shù)量由XNU內(nèi)核來決定莺掠,將上面的案例調(diào)整為8個任務(wù)衫嵌,可以看到有些線程是重復(fù)的,表明了如果一開始就可以開辟8個線程彻秆,就不會出現(xiàn)這種情況楔绞,所以當(dāng)一個任務(wù)結(jié)束之后,下一個任務(wù)可以放到完成的線程中唇兑,但是和串行隊列還是有區(qū)別的

多個任務(wù)的并發(fā)隊列

首先會開辟4條線程處理任務(wù)酒朵,將task0-task3異步形式放入線程中執(zhí)行,假定task0首先完成扎附,并發(fā)隊列中取出task4放入線程3中執(zhí)行蔫耽,以此類推,所以也就造成了有些線程是重復(fù)的原因留夜。

DISPATCH MAIN QUEUE/DISPATCH GLOBAL QUEUE

我們平時常用的大多是這兩種匙铡,前者是在主線程執(zhí)行的queue,由于主線程只有一個碍粥,自然是個串行隊列鳖眼,而global_queue是個并發(fā)隊列,這個隊列有4個優(yōu)先級嚼摩,分別是低優(yōu)先級(DISPATCH_QUEUE_PRIORITY_LOW)钦讳,默認(rèn)優(yōu)先級(DISPATCH_QUEUE_PRIORITY_DEFAULT),高優(yōu)先級(DISPATCH_QUEUE_PRIORITY_HIGH)枕面,和后臺優(yōu)先級(

DISPATCH_QUEUE_PRIORITY_BACKGROUND)愿卒,代碼如下

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

GCD的api

前面已經(jīng)提到一些關(guān)于GCD的api,下面會詳細(xì)的探討這些api的功能

dispatch_queue_create

dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.example.gcdDeo",NULL);

這里是創(chuàng)建一個queue的方法潮秘,返回類型為dispatch_queue_t琼开,方法含有兩個參數(shù),一個是隊列名枕荞,推薦使用應(yīng)用程序ID逆序的域名方式稠通,后面參數(shù)表示生成的隊列類型,一種為DISPATCH_QUEUE_SERIAL為串行隊列买猖,這里也可以使用NULL,這個宏定義的值就是NULL滋尉,另一種是DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊列玉控,這里雖然串行隊列中的任務(wù)是按順序執(zhí)行的,但是如果創(chuàng)建多個串行隊列狮惜,是會并行處理高诺。

并行執(zhí)行的多個Serial Dispatch Queue

由于一個隊列只操作一個線程碌识,所以在執(zhí)行一些比較重要的操作時,盡量使用串行隊列虱而,避免造成數(shù)據(jù)沖突筏餐。

dispatch_(a)sync

最常用的就是這兩個函數(shù)了dispatch_sync同步執(zhí)行,dispatch_async異步執(zhí)行牡拇,第一個參數(shù)為指定的隊列名魁瞪,block為執(zhí)行的任務(wù)內(nèi)容,但是使用gcd也要注意一些問題

NSLog(@"1");dispatch_sync(dispatch_get_main_queue(),^{NSLog(@"2");});NSLog(@"3");

1//接下來報錯

同步主隊列的問題

看得出這個問題的原因惠呼,同步的方式在主線程執(zhí)行一個任務(wù)导俘,造成了線程阻塞。

dispatch_after

當(dāng)我們需要在一段時間之后延遲執(zhí)行某些任務(wù)的時候剔蹋,可以使用dispatch_after這個函數(shù)旅薄,示例代碼如下:

NSLog(@"延遲之前");dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3*NSEC_PER_SEC));dispatch_after(time,dispatch_get_main_queue(),^{NSLog(@"延遲3秒執(zhí)行");});

這里的返回結(jié)果為

2018-09-04 14:52:59.437518+0800 GCDDemo[75192:21501573] 延遲之前2018-09-04 14:53:02.438042+0800 GCDDemo[75192:21501573] 延遲3秒執(zhí)行

可以看得出的確是延遲了3秒執(zhí)行的任務(wù),不過這里的實際意義為在3秒后將任務(wù)添加到主隊列中執(zhí)行泣崩,由于Main Dispatch Queue在主線程的Runloop中少梁,所以真實的時間為3秒加上最短立刻最長Runloop一次循環(huán)的時間,并且如果Runloop中堆積的任務(wù)較多矫付,可能時間會更長凯沪,所以時間并不是完全精確,不過想要大致完成延遲功能技即,dispatch_after是完全沒有問題的著洼。

time為dispatch_time_t類型,從第一個參數(shù)時間開始而叼,到第二個指定后的時間結(jié)束身笤,這里NSEC_PER_SEC是時間類型,以秒為單位葵陵,NSEC_PER_MSEC是以毫秒做單位液荸,其他類型單位這里不多解釋了。

dispatch_group

項目中我們經(jīng)常會遇到將多個異步任務(wù)的結(jié)果同時返回脱篙,如果只是同步的話娇钱,執(zhí)行完最后一個任務(wù)就是結(jié)束,但是異步卻不行绊困,所以這里我們需要使用dispatch_group文搂,代碼如下

NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_get_global_queue(0,0);dispatch_group_async(group,queue,^{sleep(4);NSLog(@"子線程1-----%@",[NSThread currentThread]);});dispatch_group_async(group,queue,^{sleep(3);NSLog(@"子線程2-----%@",[NSThread currentThread]);});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});

這里使用sleep模擬了兩個耗時操作,并且打印了幾個位置所處的線程秤朗,首先使用dispatch_group_create創(chuàng)建了一個組煤蹭,并且使用了global queue,接下來模擬異步延時操作,使用dispatch_group_async這個函數(shù)和dispatch_async的功能是一樣的硝皂,只不過前者歸屬于第一個參數(shù)這個組常挚,當(dāng)函數(shù)全部結(jié)束之后會調(diào)用dispatch_group_notify這個方法,表示所有異步操作都已經(jīng)結(jié)束稽物,代碼執(zhí)行結(jié)果如下

2018-09-0416:22:03.449143+0800GCDDemo[76086:21787405]全部開始-----<NSThread:0x6040002601c0>{number=1,name=main}2018-09-04 16:22:06.451174+0800 GCDDemo[76086:21787469] 子線程2-----<NSThread:0x60400027ed40>{number=3,name=(null)}2018-09-04 16:22:07.452564+0800 GCDDemo[76086:21787470] 子線程1-----<NSThread:0x600000470100>{number=4,name=(null)}2018-09-04 16:22:07.452926+0800 GCDDemo[76086:21787405] 全部結(jié)束-----<NSThread:0x6040002601c0>{number=1,name=main}

這里可以看出的確是當(dāng)兩個子線程都完成之后奄毡,才回到主線程的回調(diào)中,另外一種方式通過dispatch_group_wait方式阻止后續(xù)操作贝或,直到異步函數(shù)全部完成

dispatch_group_wait(group,DISPATCH_TIME_FOREVER);NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);

這里wait表示等待吼过,第一個參數(shù)表示等待的對象,類型是dispatch_group_t傀缩,第二個參數(shù)表示等待時間那先,這里使用DISPATCH_TIME_FOREVER表示一直等待,所以這個函數(shù)變成了直到組中代碼全部結(jié)束赡艰,等待才會停止售淡,不過這里還是更加推薦使用dispatch_group_notify方式,因為dispatch_group_wait是同步的慷垮,所以不推薦在主線程中使用揖闸。

dispatch_group_enter/dispatch_group_leave

實際工作中,會出現(xiàn)讓多個請求同時返回結(jié)果的案例料身,這時如果用上面的方法會造成一定問題汤纸,因為上面異步的任務(wù)只是一個NSLog,如果是一個延時請求呢芹血,下面模擬一下多個請求同時發(fā)生的案例贮泞。

NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(4);NSLog(@"模擬請求1-----%@",[NSThread currentThread]);});});dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"模擬請求2-----%@",[NSThread currentThread]);});});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});

這里在dispatch_group_async這些個異步任務(wù)中,再次用異步的方式模擬了一個請求任務(wù)幔烛,下面看一下結(jié)果

2018-09-0416:50:50.221045+0800GCDDemo[76421:21817204]全部開始-----<NSThread:0x60000007ecc0>{number=1,name=main}2018-09-04 16:50:50.249321+0800 GCDDemo[76421:21817204] 全部結(jié)束-----<NSThread:0x60000007ecc0>{number=1,name=main}2018-09-04 16:50:53.225810+0800 GCDDemo[76421:21817394] 模擬請求2-----<NSThread:0x604000663000>{number=5,name=(null)}2018-09-04 16:50:54.225917+0800 GCDDemo[76421:21817393] 模擬請求1-----<NSThread:0x6040004649c0>{number=3,name=(null)}

造成這樣的原因是發(fā)起請求的兩個任務(wù)已經(jīng)完成了啃擦,所以調(diào)用了notify方法,但是請求的結(jié)果還沒有成功饿悬,所以這樣的代碼是有問題的令蛉,那么這里就使用dispatch_group_enter和dispatch_group_leave這對方法來解決。

NSLog(@"全部開始-----%@",[NSThread currentThread]);dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create("com.example.gcdDemo",DISPATCH_QUEUE_CONCURRENT);dispatch_group_async(group,queue,^{dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(0,0),^{sleep(4);NSLog(@"模擬請求1-----%@",[NSThread currentThread]);dispatch_group_leave(group);});});dispatch_group_async(group,queue,^{dispatch_group_enter(group);dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"模擬請求2-----%@",[NSThread currentThread]);dispatch_group_leave(group);});});dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"全部結(jié)束-----%@",[NSThread currentThread]);});

這樣的結(jié)果就沒有問題了狡恬,這對方法可以理解為retain和release珠叔,當(dāng)每次進入到一個新的異步任務(wù)中時,使用dispatch_group_enter告訴原本的異步任務(wù)這里還沒有結(jié)束弟劲,當(dāng)請求完成之后使用dispatch_group_leave表示已經(jīng)結(jié)束了祷安,可以退出這個異步操作了,這樣的話兔乞,才能保證真正完成了一個延時函數(shù)汇鞭,結(jié)果如下

2018-09-0416:54:22.832901+0800GCDDemo[76462:21821447]全部開始-----<NSThread:0x60000007e980>{number=1,name=main}2018-09-04 16:54:25.837875+0800 GCDDemo[76462:21821502] 模擬請求2-----<NSThread:0x604000460840>{number=3,name=(null)}2018-09-04 16:54:26.833634+0800 GCDDemo[76462:21821503] 模擬請求1-----<NSThread:0x6000004676c0>{number=4,name=(null)}2018-09-04 16:54:26.833992+0800 GCDDemo[76462:21821447] 全部結(jié)束-----<NSThread:0x60000007e980>{number=1,name=main}

當(dāng)前案例還有其他的解決方案撇眯,我準(zhǔn)備在下一篇文章中將案例的其他解決辦法寫出來,這里不加贅述了虱咧。

dispatch_barrier_(a)sync

再次通過一個案例來認(rèn)識這個函數(shù),假設(shè)有a锚国,b腕巡,c,d四個異步任務(wù)血筑,我新增加了一個任務(wù)new绘沉,我希望可以在a和b之后完成并且在c和d之前完成,這里可以通過同步等其他方式解決豺总,不過有個更好的方式解決這個問題车伞,dispatch_barrier_async,和它的名字一樣喻喳,可以理解為一個柵欄另玖,將前后分隔開,在下先上代碼

dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue,^{sleep(3);NSLog(@"task - A");});dispatch_async(queue,^{sleep(2);NSLog(@"task - B");});NSLog(@"before barrier");dispatch_barrier_sync(queue,^{NSLog(@"task - new");});NSLog(@"after barrier");dispatch_async(queue,^{sleep(2);NSLog(@"task - C");});dispatch_async(queue,^{sleep(1);NSLog(@"task - D");});

這里模擬了四個異步操作任務(wù)表伦,將中間位置插入了一個new任務(wù)谦去,這里看看返回結(jié)果

2018-09-04 17:55:08.783989+0800 GCDDemo[76920:21886183] before barrier2018-09-04 17:55:10.787647+0800 GCDDemo[76920:21886310] task - B2018-09-04 17:55:11.785806+0800 GCDDemo[76920:21886309] task - A2018-09-04 17:55:11.786003+0800 GCDDemo[76920:21886183] task - new2018-09-04 17:55:11.786164+0800 GCDDemo[76920:21886183] after barrier2018-09-04 17:55:12.790452+0800 GCDDemo[76920:21886312] task - D2018-09-04 17:55:13.790432+0800 GCDDemo[76920:21886309] task - C

這里可以看出,由于有模擬延時操作蹦哼,所以before首先輸出鳄哭,然后執(zhí)行了A和B兩個任務(wù),而barrier中的任務(wù)并沒有延時卻在A和B之后纲熏,說明柵欄功能生效妆丘,有因為是sync同步的關(guān)系,所以后面的after緊接著輸出局劲,這里把sync換成async會有什么結(jié)果呢

2018-09-04 17:59:29.114406+0800 BlockDemo[76948:21890748] before barrier2018-09-04 17:59:29.114600+0800 BlockDemo[76948:21890748] after barrier

before和after是緊挨著輸出的勺拣,說明dispatch_barrier_sync同樣有同步的作用,而dispatch_barrier_async也有著異步的效果容握。

dispatch_apply

如果有一個案例需要按指定次數(shù)執(zhí)行g(shù)cd的任務(wù)宣脉,可以用這個函數(shù),第一個參數(shù)為次數(shù)剔氏,第二個參數(shù)為指定的隊列塑猖,第三個參數(shù)是帶參數(shù)的block,參數(shù)為當(dāng)前次數(shù)下標(biāo)

NSArray*array=@[@"1",@"2",@"3",@"4",@"5",];dispatch_apply([array count],dispatch_get_global_queue(0,0),^(size_t index){NSLog(@"元素:%@----第%ld次",array[index],index);});

2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942336] 元素:4----第3次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942205] 元素:1----第0次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942337] 元素:3----第2次2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942335] 元素:2----第1次2018-09-04 18:44:56.970380+0800 GCDDemo[77406:21942205] 元素:5----第4次

dispatch_semaphore

前文已經(jīng)有講部分關(guān)于產(chǎn)生不一樣數(shù)據(jù)的處理問題谈跛,不過有時需要更細(xì)致的排他控制羊苟,例如你到了一個屋子,里面有一個椅子感憾,你可以坐下蜡励,如果再拿來一個椅子,依然可以坐下,但是如果把椅子拿走凉倚,那么只能站著等待了兼都,semaphore使用信號量的方式實現(xiàn)了類似的情況,首先第一個函數(shù):

dispatch_semaphore_create(0);

這里創(chuàng)建了一個semaphore稽寒,返回類型是dispatch_semaphore_t扮碧,有一個參數(shù),表示初始信號量的值杏糙,這里給0,如果遇到wait則需要等待赖淤。

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);dispatch_semaphore_signal(semaphore);

當(dāng)?shù)谝粋€函數(shù)中的信號量0時,這個函數(shù)需要進行等待咱旱,如果大于0莽龟,則將信號量-1锨天,第二個參數(shù)為等待的時間,這個可以根據(jù)需求使用搂赋,這里假定需要等待無限長的時間脑奠,知道信號量增加未知

第二個函數(shù)中會給信號量+1幅慌,可以看出這兩個函數(shù)是要成對出現(xiàn)的胰伍,如果單獨出現(xiàn)wait而且初始化為0的話骂租,會造成后續(xù)任務(wù)全部卡住無法執(zhí)行渗饮。下面給一個小的案例

dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_semaphore_t semaphore=dispatch_semaphore_create(0);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(3);NSLog(@"完成1");dispatch_semaphore_signal(semaphore);});});dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{sleep(2);NSLog(@"完成2");dispatch_semaphore_signal(semaphore);});});dispatch_group_notify(group,queue,^{dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);NSLog(@"全部完成");});

這里再次模擬了多個請求需要同時返回結(jié)果的問題,通過信號量的方式私蕾,使用信號量的方式同樣可以完成這個需求踩叭,當(dāng)notify中有兩個等待信號的時候,只能通過請求成功的信號量增加的方法去抵消,當(dāng)兩個請求全部完成的時候嗤疯,等待信號也全部結(jié)束茂缚,這時表示任務(wù)全部完成脚囊。

不過如果更換一下需求會怎么樣呢桐磁,如果我們需要讓多個請求同步執(zhí)行要怎么做我擂,首先我們需要開啟異步去管理校摩,同樣請求也是異步方法,所以我們用這種方式不能讓請求同步執(zhí)行互妓,這里需要使用線程依賴的方式操作冯勉,GCD線程依賴這里面也使用semaphore的方式去做珠闰,修改代碼如下

dispatch_group_t group=dispatch_group_create();dispatch_queue_t queue=dispatch_queue_create(NULL,DISPATCH_QUEUE_CONCURRENT);dispatch_semaphore_t semaphore0=dispatch_semaphore_create(0);dispatch_semaphore_t semaphore1=dispatch_semaphore_create(0);dispatch_group_async(group,queue,^{dispatch_async(dispatch_get_global_queue(0,0),^{[NSThread sleepForTimeInterval:3];NSLog(@"完成1");dispatch_semaphore_signal(semaphore0);});});dispatch_group_async(group,queue,^{dispatch_semaphore_wait(semaphore0,DISPATCH_TIME_FOREVER);dispatch_async(dispatch_get_global_queue(0,0),^{[NSThread sleepForTimeInterval:2];NSLog(@"完成2");dispatch_semaphore_signal(semaphore1);});});dispatch_group_notify(group,queue,^{dispatch_semaphore_wait(semaphore1,DISPATCH_TIME_FOREVER);NSLog(@"全部完成");});

這里創(chuàng)建了兩個信號量伏嗜,因為任務(wù)1模擬了時間更久承绸,我們這里需要讓任務(wù)1先完成,那么我們在任務(wù)2中把任務(wù)1的信號量等待轩猩,直到任務(wù)1完成并增加信號量均践,再執(zhí)行任務(wù)2摩幔,結(jié)果如下

2018-09-05 10:27:02.725024+0800 GCDDemo[81615:22654482] 完成12018-09-05 10:27:04.727478+0800 GCDDemo[81615:22654482] 完成22018-09-05 10:27:04.727852+0800 GCDDemo[81615:22654485] 全部完成

可以看出我們的目的已經(jīng)實現(xiàn)了或衡,但是如果有多個請求任務(wù)呢封断,必然需要創(chuàng)建多個信號量,按照需要的順序進行依賴彬呻,但是這種方法其實寫起來容易亂废岂,最好的方式是使用NSOperationQueue添加線程依賴湖苞,但是這里不多加贅述财骨,同樣后續(xù)文章中會詳細(xì)分析一次此案例隆箩,并使用其他方式解決這個問題羔杨。

dispatch_once

這個函數(shù)應(yīng)該也不會陌生兜材,當(dāng)我們希望只會執(zhí)行一次的函數(shù)我們會使用dispatch_once,所有我們創(chuàng)建單例的時候也會使用到這個函數(shù)寇荧。

+(instancetype)shareInstance{staticManager*manager=nil;staticdispatch_once_t onceToken;dispatch_once(&onceToken,^{manager=[Manager new];});returnmanager;}

這種單例不需要擔(dān)心線程問題揩抡,即使是多線程環(huán)境下也一定是安全的峦嗤,onceToken會保證運行過程中這部分只會執(zhí)行一次寻仗。

GCD的實現(xiàn)

GCD的使用是十分方便的,這里探討一下它是如何實現(xiàn)的

C語言實現(xiàn)的用于管理追加Block的FIFO隊列

用于排他控制的atomic類型的輕量級信號

C語言實現(xiàn)的管理線程的容器

首先確定一下用于實現(xiàn)dispatch queue的軟件組件

組件和技術(shù)

我們所使用的全部api都處于libdispatch庫中亚侠,Dispatch Queue通過結(jié)構(gòu)體和鏈表實現(xiàn)FIFO隊列硝烂。FIFO通過dispatch_async等函數(shù)管理添加的block滞谢。

但是block并不是直接加入到FIFO隊列中除抛,而是先加入Dispatch Continuation這個dispatch_continuation_t類型的結(jié)構(gòu)體中,再加入到隊列中橄教,這個結(jié)構(gòu)體包含了block所屬的group等信息护蝶,也就是執(zhí)行上下文翩迈。

XNU內(nèi)核有4中workqueue持灰,優(yōu)先級和Global Dispatch Queue的優(yōu)先級相同,當(dāng)在Global Dispatch Queue中執(zhí)行block時负饲,libdispatch從FIFO隊列中取出Dispatch Continuation堤魁,調(diào)用pthread_workqueue_additem_np函數(shù)喂链,將自身信息,符合其優(yōu)先級的workqueue信息以及執(zhí)行回調(diào)函數(shù)等傳遞給參數(shù)姨涡。

pthread_work_queue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用衩藤。通知workqueue應(yīng)當(dāng)執(zhí)行的項目。根據(jù)通知涛漂,XNU內(nèi)核基于系統(tǒng)判斷是否生成線程赏表,如果是overcommit屬性則始終生成線程。

workqueue的線程執(zhí)行pthread_workqueue函數(shù)瓢剿,該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)火架,在回調(diào)函數(shù)中執(zhí)行加入到Dispatch Continuation的Block。

Block執(zhí)行結(jié)束后淆游,進行通知Dispatch Group結(jié)束腊脱、釋放Dispatch Continuation等處理俱笛。開始準(zhǔn)備執(zhí)行Global Dispatch Queue的下一個Block

以上就是Dispatch Queue的大致執(zhí)行過程。

Dispatch Source

GCD中除了常用的Dispatch Queue外簸呈,還有Dispatch Source贩幻,它具有很多種類型處理能力,最常見的就是使用定時器。

dispatch_source_t timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,dispatch_get_main_queue());//創(chuàng)建一個dispatch_source_t類型變量,類型指定為定時器dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC,0);//指定定時器執(zhí)行時間為每秒執(zhí)行一次dispatch_source_set_event_handler(timer,^{NSLog(@"定時內(nèi)容");//每秒執(zhí)行的內(nèi)容});dispatch_source_set_cancel_handler(timer,^{NSLog(@"定時取消");//取消定時器的回調(diào)});dispatch_resume(timer);//啟動定時器//dispatch_source_cancel(timer);//取消定時器

這里本身會存在一個問題,就是set_event_handle這個回調(diào)會不執(zhí)行玲昧,原因是當(dāng)執(zhí)行過作用域之后亲配,這個source可能會被釋放掉犬钢,所以可以使用添加到屬性的方式放大source的作用域歹颓,保證定時器可以始終執(zhí)行

@property(nonatomic,strong)dispatch_source_t timer;

這里如果內(nèi)存管理語義使用了assign創(chuàng)建定時器,則會報出會被釋放的錯誤。

使用Dispatch Queue本身是不具備“取消”功能的,要么放棄取消,要么使用NSOperationQueue等方法,而Dispatch Source具備該功能,而且取消后執(zhí)行的處理可以使用block形式牵寺,這里也能看出Dispatch Source的強大功能。

到此為止關(guān)于GCD的這篇文章就結(jié)束了,如果文章中出現(xiàn)問題歡迎指出,并且如果有更優(yōu)秀的看法也歡迎提出或討論。

本文部分內(nèi)容參考自《Objective-C高級編程》一書狸眼,有興趣的小伙伴可以翻看一下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浴滴,一起剝皮案震驚了整個濱河市拓萌,隨后出現(xiàn)的幾起案子微王,更是在濱河造成了極大的恐慌,老刑警劉巖品嚣,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罩旋,死亡現(xiàn)場離奇詭異宪潮,居然都是意外死亡芬为,警方通過查閱死者的電腦和手機资厉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔬顾,“玉大人宴偿,你說我怎么就攤上這事【骰恚” “怎么了窄刘?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舷胜。 經(jīng)常有香客問我娩践,道長,這世上最難降的妖魔是什么逞带? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任欺矫,我火速辦了婚禮,結(jié)果婚禮上展氓,老公的妹妹穿的比我還像新娘穆趴。我一直安慰自己,他們只是感情好遇汞,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布未妹。 她就那樣靜靜地躺著,像睡著了一般空入。 火紅的嫁衣襯著肌膚如雪络它。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天歪赢,我揣著相機與錄音化戳,去河邊找鬼。 笑死埋凯,一個胖子當(dāng)著我的面吹牛点楼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播白对,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掠廓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甩恼?” 一聲冷哼從身側(cè)響起蟀瞧,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沉颂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悦污,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铸屉,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年切端,在試婚紗的時候發(fā)現(xiàn)自己被綠了抬探。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡帆赢,死狀恐怖小压,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椰于,我是刑警寧澤怠益,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站瘾婿,受9級特大地震影響蜻牢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜偏陪,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一抢呆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笛谦,春花似錦抱虐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灶轰,卻和暖如春谣沸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笋颤。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工乳附, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伴澄。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓赋除,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秉版。 傳聞我的和親對象是個殘疾皇子贤重,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354