iOS從實(shí)際出發(fā)理解多線程
前言
多線程很多開發(fā)者多多少少相信也都有了解,以前有些東西理解的不是很透,慢慢的積累之后,這方面的東西也需要自己好好的總結(jié)一下。多線程從我剛接觸到iOS的時(shí)候就知道這玩意挺重要的,但那時(shí)也是能力有限,沒辦法很好的理解它,要是只是查它的概念性的東西,網(wǎng)上一搜一大把,我們?cè)倌菢尤タ偨Y(jié)就顯得意義不大了。這篇文章從我剛開始構(gòu)思著去寫的時(shí)候,就希望自己能換個(gè)角度去寫,想從實(shí)際問(wèn)題出發(fā)總結(jié)多線程,那就從第三方以及自己看到的一些例子還有前段時(shí)間讀的多線程和內(nèi)存管理的書中分析理解總結(jié)一下多線程。
這幾個(gè)概念很容易繞暈
一 進(jìn)程:進(jìn)程就是線程的容器,你打開一個(gè)App就是打開了一個(gè)進(jìn)程,QQ有QQ的進(jìn)程,微信有微信的進(jìn)程,一個(gè)進(jìn)程可以包含多個(gè)線程,要是把進(jìn)程比喻成一條高速公路,線程就是高速路上的一條條車道,也正是因?yàn)橛辛诉@些車道,整個(gè)交通的運(yùn)行效率變得更高,也正是因?yàn)橛辛硕嗑€程的出現(xiàn),整個(gè)系統(tǒng)運(yùn)行效率變得更高。
二 線程:線程就是在進(jìn)程中我么開辟的一條條為我們做事的進(jìn)程實(shí)體,總結(jié)的通俗一點(diǎn),線程就是我們?cè)谶M(jìn)程上開辟的一條條做我們想做的事的通道。 一條線程在一個(gè)時(shí)間點(diǎn)上只能做一件“事”,多線程在同一時(shí)間點(diǎn)上,就能做多件“事”,這個(gè)理解,還是我們前面說(shuō)的高速路的例子。
一條高速路是一個(gè)進(jìn)程, 一條條車道就是不同的線程,在過(guò)收費(fèi)站的時(shí)候,這條進(jìn)程上要是只有一條線程,也就是一條高速路上只有一個(gè)車道,那你就只能排隊(duì)一輛一輛的通過(guò),同一時(shí)間不可能有兩輛車一起過(guò)去,但要是你一個(gè)進(jìn)程上有多個(gè)線程,也就是高速路上有幾個(gè)車道,也就有多個(gè)窗口收費(fèi),這樣的話同一時(shí)間就完全有可能兩輛車一起交完費(fèi)通過(guò)了,這樣說(shuō)相信也能理解這個(gè)進(jìn)程和線程的關(guān)系了。
- 同步線程:同步線程會(huì)阻塞當(dāng)前的線程去執(zhí)行同步線程里面想做的“事”(任務(wù)),執(zhí)行完之后才會(huì)返回當(dāng)前線程。
- 異步線程:異步線程不會(huì)阻塞當(dāng)前的線程去執(zhí)行異步線程里面想做的“事”,因?yàn)槭钱惒剑运鼤?huì)重新開啟一個(gè)線程去做想做的“事”。
三 隊(duì)列:隊(duì)列就是用來(lái)管理下面說(shuō)的“任務(wù)”的,它采用的是先進(jìn)先出(FIFO)的原則,它衍生出來(lái)的就是下面的它的分類并行和串行隊(duì)列,一條線程上可以有多個(gè)隊(duì)列。
- 并行隊(duì)列:這個(gè)隊(duì)列里面的任務(wù)是可以并發(fā)(同時(shí))執(zhí)行的,由于我們知道,同步執(zhí)行任務(wù)不會(huì)開啟新的線程,所以并行隊(duì)列同步執(zhí)行任務(wù)任務(wù)只會(huì)在一條線程里面同步執(zhí)行這些任務(wù),又由于同步執(zhí)行也就是在當(dāng)前線程中做事,這個(gè)時(shí)候就需要一件一件的讓“事”(任務(wù))做完在接著做下一個(gè)。但要是是并發(fā)隊(duì)列異步執(zhí)行,就對(duì)應(yīng)著開啟異步線程執(zhí)行要做的“事”(任務(wù)),就會(huì)同一時(shí)間又許多的“事”被做著。
- 串行隊(duì)列:這個(gè)隊(duì)列里面的任務(wù)是串行也就是一件一件做的,串行同步會(huì)一件一件的等事做完再接著做下一件,要是異步的就會(huì)開啟一條新的線程串行的執(zhí)行我們的任務(wù)。
四 任務(wù):任務(wù)按照自己通俗一點(diǎn)的理解,就是提到的“事”這個(gè)概念,這個(gè)“事”就可以理解為任務(wù),那這個(gè)“事”也肯定是在線程上面執(zhí)行的(不管是在當(dāng)前線程還是你另開啟的線程)。這個(gè)“事”你可以選擇同步或者而是異步執(zhí)行,這就衍生出了東西也就契合線程上面的同步線程和異步線程。
- 同步任務(wù):不需要開啟新的線程,在當(dāng)前線程執(zhí)行就可以。
- 異步任務(wù):你需要開辟一條新的線程去異步的執(zhí)行這個(gè)任務(wù)。
iOS當(dāng)中還有一個(gè)特殊的串行隊(duì)列-- 主隊(duì)列, 這個(gè)主隊(duì)列中運(yùn)行著一條特殊的線程 -- 主線程
主線程又叫UI線程,UI線程顧名思義主要的任務(wù)及時(shí)處理UI,也只有主線程有處理UI的能力,其他的耗時(shí)間的操作我們就放在子線程(也就是開辟線程)去執(zhí)行,開線程也會(huì)占據(jù)一定的內(nèi)存的,所以不要同時(shí)開啟很多的線程。
通過(guò)上面的內(nèi)容解釋了多線程里面幾個(gè)關(guān)鍵的概念的東西,要是有不理解的地方歡迎多交流,下面再給出隊(duì)列執(zhí)行時(shí)候的一個(gè)運(yùn)行的表格,我們一個(gè)一個(gè)慢慢的解釋。

NSThread
其實(shí)在我們?nèi)粘5拈_發(fā)中NSThread使用也是挺多的,具體關(guān)于它的一些我們需要注意的地方我們一步步的開始說(shuō),先看看它的初始化的幾個(gè)方法
- /*
- 初始化NSThread的類方法,具體的任務(wù)在Block中執(zhí)行
- + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- 利用selector方法初始化NSThread,target指selector方法從屬于的對(duì)象 selector方法也是指定的target對(duì)象的方法
- + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
- 初始化NSThread的方法,這兩個(gè)方法和上面兩個(gè)方法的區(qū)別就是這兩個(gè)你能獲取到NSThread的對(duì)象
- 具體的參數(shù)和前面解釋的參數(shù)意義都是一樣的
- 切記一點(diǎn): 下面兩個(gè)方法初始化的NSThread你需要手動(dòng)start開啟線程
- - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
- - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- */
除了上面四個(gè)我們提出的方法,我們?cè)诔跏蓟@個(gè)問(wèn)題上還需要注意的還有一點(diǎn),就是 NSObject (NSThreadPerformAdditions) ,為我們的NSObject添加的這個(gè)類別,它里面的具體的一些方法我們也是很常用的:
- /*
- 這個(gè)方法你執(zhí)行的aSelector就是在MainThread執(zhí)行的,也就是在主線程
- 注意這里的waitUntilDone這個(gè)后面的BOOL類型的參數(shù),這個(gè)參數(shù)表示是否等待一直到aSelector這個(gè)方法執(zhí)行結(jié)束
- modes是RunLoop的運(yùn)行的類型這個(gè)RunLoop我也會(huì)好好在總結(jié)后面
- - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- // equivalent to the first method with kCFRunLoopCommonModes
- 上面的兩個(gè)方法是直接在主線程里面運(yùn)行,下面的這兩個(gè)方法是要在你初始化的thr中去運(yùn)行,其他的參數(shù)和上面解釋的一樣
- - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array NS_AVAILABLE(10_5, 2_0);
- - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- // equivalent to the first method with kCFRunLoopCommonModes
- - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
- */
我們?cè)谡f(shuō)說(shuō)前面說(shuō)的waitUntilDone后面的這個(gè)BOOL類型的參數(shù),這個(gè)參數(shù)的意義有點(diǎn)像我們是否同步執(zhí)行aSelector這個(gè)任務(wù)!具體的看下面兩張圖的內(nèi)容就一目了然了:

在看看等于YES的時(shí)候結(jié)果的輸出情況:

關(guān)于NSThread我們?cè)僬f(shuō)下面幾個(gè)方法的具體的含義就不在描述了,關(guān)于NSThread有什么其他的問(wèn)題,可以加我QQ交流:
- /*
- 設(shè)置線程沉睡到指定日期
- + (void)sleepUntilDate:(NSDate *)date;
- 線程沉睡時(shí)間間隔,這個(gè)方法在設(shè)置啟動(dòng)頁(yè)間隔的時(shí)候比較常見
- + (void)sleepForTimeInterval:(NSTimeInterval)ti;
- 線程退出,當(dāng)執(zhí)行到某一個(gè)特殊情況下的時(shí)候你可以退出當(dāng)前的線程,注意不要在主線程隨便調(diào)用
- + (void)exit;
- 線程的優(yōu)先級(jí)
- + (double)threadPriority;
- 設(shè)置線程的優(yōu)先級(jí)
- + (BOOL)setThreadPriority:(double)p;
- */
NSOperation
多線程我們還得提一下NSOperation,它可能比我們認(rèn)識(shí)中的要強(qiáng)大一點(diǎn),NSOperation也是有很多東西可以說(shuō)的,前面的NSThread其實(shí)也是一樣,這些要是仔細(xì)說(shuō)的話都能寫一篇文章出來(lái),可能以后隨著自己接觸的越來(lái)越多,關(guān)于多線程這一塊的東西我們會(huì)獨(dú)立的創(chuàng)建一個(gè)分類總結(jié)出去。
首先得知道NSOperation是基于GCD封裝的,NSOperation這個(gè)類本身我們使用的時(shí)候不躲,更多的是集中在蘋果幫我們封裝好的NSInvocationOperation和NSBlockOperation
你command一下NSOperation進(jìn)去看看,有幾個(gè)點(diǎn)你還是的了解一下的,主要的就是下面的幾個(gè)方法:
- NSOperation * operation = [[NSOperation alloc]init];
- [operation start]; //開始
- [operation cancel]; //取消
- [operation setCompletionBlock:^{
- //operation完成之后的操作
- }];
我們具體的說(shuō)一下我們上面說(shuō)的兩個(gè)類:NSInvocationOperation和NSBlockOperation,先看看NSInvocationOperation的初始化:
- /*
- 初始化方法 看過(guò)前面的文章之后它的target 、sel 、arg 等參數(shù)相信不難理解
- -(nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- -(instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
- */
補(bǔ)充: NS_DESIGNATED_INITIALIZER 指定初識(shí)化方法并不是對(duì)使用者。而是對(duì)內(nèi)部的現(xiàn)實(shí),可以點(diǎn)擊進(jìn)去具體了解一下它!NSInvocationOperation其實(shí)是同步執(zhí)行的,因此單獨(dú)使用的話就價(jià)值不大了,它和NSOperationQueue一起去使用才能實(shí)現(xiàn)多線程調(diào)用。這個(gè)我們后面再具體的說(shuō)
在看看NSBlockOperation這個(gè),它重要的方法就我們下面的兩個(gè)
- /*
- 初始化方法
- + (instancetype)blockOperationWithBlock:(void (^)(void))block;
- 添加一個(gè)可以執(zhí)行的block到前面初始化得到的NSBlockOperation中
- - (void)addExecutionBlock:(void (^)(void))block;
- */
NSBlockOperation這個(gè)我們得提一點(diǎn): 它的最大的并發(fā)具體的最大并發(fā)數(shù)和運(yùn)行環(huán)境也是有關(guān)系的,具體的內(nèi)容我們可以戳戳這里同行總結(jié)以及驗(yàn)證的,我們由于篇幅的原因就不在這里累贅。
其實(shí)只要是上面這些的話是不夠我們?nèi)粘J褂玫模€有一個(gè)激活他們倆的類我們也得說(shuō)說(shuō):NSOPerationQueue 下面是關(guān)于它的大概的一個(gè)說(shuō)明,都挺簡(jiǎn)單,就不在特意寫Demo。

關(guān)于NSOperation的我們就說(shuō)這么多,下面重點(diǎn)說(shuō)一下GCD。
主角GCD -- 主線程
1、我們先從主隊(duì)列,主線程開始說(shuō)起,通過(guò)下面的方法我們就可以獲取得到主隊(duì)列:
- dispatch_queue_t mainqueue = dispatch_get_main_queue();
2、我們?cè)谥骶€程同步執(zhí)行任務(wù),下面是操作的結(jié)果以及打印的信息:

我們解釋一下為什么在主線程中執(zhí)行同步任務(wù)會(huì)出現(xiàn)這個(gè)結(jié)果,我們一步一步的梳理一下這個(gè)執(zhí)行過(guò)程:
- 獲取到在主隊(duì)列主線程中執(zhí)行了最前面的打印信息,這個(gè)沒什么問(wèn)題
- 開始執(zhí)行dispatch_sync這個(gè)函數(shù),主隊(duì)列是串行隊(duì)列,這個(gè)函數(shù)會(huì)把這個(gè)任務(wù)插入到主隊(duì)列的最后面(理解隊(duì)列添加任務(wù))
- 主線程執(zhí)行到這里的時(shí)候就會(huì)等待插入的這個(gè)同步任務(wù)執(zhí)行完之后再執(zhí)行后面的操作
- 但由于這個(gè)同步任務(wù)是插入到主隊(duì)列的最后面,最隊(duì)列前面的任務(wù)沒有執(zhí)行完之前是不會(huì)執(zhí)行這個(gè)block的(主線程在執(zhí)行initMainQueue任務(wù))
- 這樣就造成了一個(gè)相互等待的過(guò)程,主線程在等待block完返回,block卻在等待主線程執(zhí)行它,這樣就造成了死鎖,看打印的信息你也就知道block是沒有被執(zhí)行的。
這里我們你可能會(huì)思考,主隊(duì)列是一個(gè)串行隊(duì)列,那我們?cè)谥骶€程中添加一個(gè)串行隊(duì)列,再給串行隊(duì)列添加一個(gè)同步任務(wù),這時(shí)候和前面主線程主隊(duì)列添加同步任務(wù)不就場(chǎng)景一樣了嗎?那結(jié)果呢? 我們看看下面的打印:

我們按照前面的方式解釋一下這個(gè)的執(zhí)行步驟:
- 主線程在執(zhí)行主隊(duì)列中的方法initSerialQueue,到這個(gè)方法時(shí)候創(chuàng)建了一個(gè)串行隊(duì)列(注意不是主隊(duì)列)打印了前面的第一條信息
- 執(zhí)行到dispatch_sync函數(shù),這個(gè)函數(shù)給這個(gè)串行隊(duì)列中添加了一個(gè)同步任務(wù),同步任務(wù)是會(huì)立馬執(zhí)行的
- 主線程就直接操作執(zhí)行了這個(gè)隊(duì)列中的同步任務(wù),打印的第二條信息
- 主線程接著執(zhí)行下面的第三條打印信息
理解:看這個(gè)執(zhí)行的過(guò)程對(duì)比前面的,你就知道了不同的地方就是前面是添加在了主隊(duì)列當(dāng)中,但這里有添加到主隊(duì)列,由于是插入到主隊(duì)列的末尾,所以需要主隊(duì)列的任務(wù)都執(zhí)行完才能指定到它,但主線程執(zhí)行到initMainQueue這個(gè)方法的時(shí)候在等待這個(gè)方法中添加的同步任務(wù)執(zhí)行完接著往下執(zhí)行,但它里面的同步任務(wù)又在等待主線程執(zhí)行完在執(zhí)行它,就相互等待了,但主線程執(zhí)行不是主隊(duì)列里面的同步任務(wù)的時(shí)候是不需要主線程執(zhí)行完所有操作在執(zhí)行這個(gè)任務(wù)的,這個(gè)任務(wù)是它添加到串行隊(duì)列的開始也是結(jié)束的任務(wù),由于不需要等待,就不會(huì)造成死鎖!
上面這個(gè)問(wèn)題經(jīng)常會(huì)看到有人問(wèn),有許多解釋,也希望自己能把這個(gè)問(wèn)題給說(shuō)清楚了!
3、主線程這里我們?cè)偬嵋稽c(diǎn),就是線程間的信息簡(jiǎn)單傳遞
前面我們有說(shuō)到主線程又叫做UI線程,所有關(guān)于UI的事我們都是在主線程里面更新的,像下載數(shù)據(jù)以及數(shù)據(jù)庫(kù)的訪問(wèn)等這些耗時(shí)的操作我們是建議放在子線程里面去做,那就會(huì)產(chǎn)生子線程處理完這些之后要回到主線程更行UI的問(wèn)題上,這一點(diǎn)值得我們好好的注意一下,但其實(shí)這一點(diǎn)也是我們用的最多的,相信大家也都理解!
主角GCD -- 串行隊(duì)列
串行隊(duì)列的概念性的東西我們就不在這里累贅,不管是串行隊(duì)列+同步任務(wù)還是串行隊(duì)列+異步任務(wù)都簡(jiǎn)單,有興趣可以自己是這寫一下,后面分析會(huì)提到他們的具體使用的,我們?cè)谝粋€(gè)稍微比前面的說(shuō)的復(fù)雜一點(diǎn)點(diǎn)的問(wèn)題,串行隊(duì)列+異步+同步,可以先試著不要往下面看先分析一下下面這段代碼的執(zhí)行結(jié)果是什么?
- static void * DISPATCH_QUEUE_SERIAL_IDENTIFY;
- -(void)initDiapatchQueue{
- dispatch_queue_t serialQueue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL_IDENTIFY, DISPATCH_QUEUE_SERIAL);
- dispatch_async(serialQueue, ^{
- NSLog(@"一個(gè)異步任務(wù)的內(nèi)容%@",[NSThread currentThread]);
- dispatch_sync(serialQueue, ^{
- NSLog(@"一個(gè)同步任務(wù)的內(nèi)容%@",[NSThread currentThread]);
- });
- });
- }
不知道你分析數(shù)來(lái)的這點(diǎn)代碼的結(jié)果是什么,我們這里來(lái)看看結(jié)果,然后和上面一步一步的分析一下它的整個(gè)的執(zhí)行過(guò)程,就能找到答案:

答案就是crash了,其實(shí)也是死鎖,下面一步一步的走一下這整個(gè)過(guò)程,分析一下哪里死鎖了:
- 主線程主隊(duì)列中執(zhí)行任務(wù)initDispatchQueue,進(jìn)入了這個(gè)方法,在這個(gè)方法里面創(chuàng)建了一個(gè)串行隊(duì)列,這一步相信大家都明白,沒什么問(wèn)題。
- 給這個(gè)串行隊(duì)列添加了一個(gè)異步任務(wù),由于是異步任務(wù),所以會(huì)開啟一條新的線程,為了方便描述,我們把新開的這個(gè)線程記做線程A, 把這個(gè)任務(wù)記做任務(wù)A,也由于是異步任務(wù),主線程就不會(huì)等待這個(gè)任務(wù)返回,就接著往下執(zhí)行其他任務(wù)了。
- 接下來(lái)的分析就到了這個(gè)線程A上,這個(gè)任務(wù)A被添加到串行隊(duì)列之后就開始在線程A上執(zhí)行,打印出了我們的第一條信息,也證明了不是在主線程,這個(gè)也沒問(wèn)題。
- 線程A開始執(zhí)行這個(gè)任務(wù)A,進(jìn)入這個(gè)任務(wù)A之后在這個(gè)任務(wù)A里面又同步在串行隊(duì)列里面添加任務(wù),記做任務(wù)B,由于任務(wù)B是dispatch_sync函數(shù)同步添加的,需要立馬被執(zhí)行,就等待線程A執(zhí)行它
- 但是這個(gè)任務(wù)B是添加到串行隊(duì)列的末尾的,線程A在沒有執(zhí)行完當(dāng)前任務(wù)A是不會(huì)去執(zhí)行它的,這樣就造成線程A在等待當(dāng)前任務(wù)A執(zhí)行完,任務(wù)B又在等待線程A執(zhí)行它,就形成了死鎖
經(jīng)過(guò)上面的分析,你就能看到這個(gè)場(chǎng)景和你在主線程同步添加任務(wù)是一樣的,我們?cè)僮屑?xì)的考慮一下這整個(gè)過(guò)程,在分析一下上面主線程+串行隊(duì)列+同步任務(wù)為什么沒有形成死鎖!相互對(duì)比理解,就能把整個(gè)問(wèn)題想明白。
主角GCD -- 并行隊(duì)列
下面我們接著再說(shuō)說(shuō)這個(gè)并行隊(duì)列,并行隊(duì)列+同步執(zhí)行或者并行隊(duì)列+異步執(zhí)行這個(gè)我們也就沒什么好說(shuō)的了,在這里說(shuō)說(shuō)并行+異步的需要注意的地方,不知道大家有沒有想過(guò),并行的話很多任務(wù)會(huì)一起執(zhí)行,要是異步任務(wù)的話會(huì)開啟新的線程,那是不是我們添加了十個(gè)異步任務(wù)就會(huì)開啟十條線程呢?那一百個(gè)異步任務(wù)豈不是要開啟一百條線程,答案肯定是否定的!那系統(tǒng)到底是怎么處理的,我們也說(shuō)說(shuō),下面的是高級(jí)編程書里面的解釋我們梳理一下給出結(jié)論。
- 當(dāng)為DISPATCH_QUEUE_CONCURRENT的時(shí)候,不用等待前面任務(wù)的處理結(jié)束,后面的任務(wù)也是能夠直接執(zhí)行的
- 并行執(zhí)行的處理數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài),即iOS和OS X基于Dispatch Queue中的處理數(shù)、CPU核數(shù)以及CPU負(fù)荷等當(dāng)前系統(tǒng)狀態(tài)來(lái)決定DISPATCH_QUEUE_CONCURRENT中并行執(zhí)行的處理數(shù)
- iOS 和 OS X的核心 -- XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù),并且生成所需的線程執(zhí)行處理
- 當(dāng)處理結(jié)束,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時(shí),XNU內(nèi)核會(huì)結(jié)束不在需要的線程
處理并行異步任務(wù)時(shí)候線程是可以循環(huán)往復(fù)使用的,比如任務(wù)1的線程執(zhí)行完了任務(wù)1,線程可以接著去執(zhí)行后面沒有執(zhí)行的任務(wù)
這里的東西就這些,我們?cè)谇懊娲嘘?duì)列的時(shí)候,串行隊(duì)列+異步任務(wù)嵌套同步任務(wù)會(huì)造成死鎖,那我們要是把它變成同步隊(duì)列呢?結(jié)果又會(huì)是什么樣子呢?我們看看下面這段代碼的執(zhí)行結(jié)果:

從上面的結(jié)果可以看得出來(lái),是沒有問(wèn)題的,這里我們就不在一步一步的分析它的執(zhí)行過(guò)程了,就說(shuō)說(shuō)為什么并行的隊(duì)列就沒有問(wèn)題,但是串行的隊(duì)列就會(huì)出問(wèn)題:
并行隊(duì)列添加了異步任務(wù)也是創(chuàng)建了一個(gè)新的線程,然后再在這個(gè)任務(wù)里面給并行隊(duì)列添加一個(gè)同步任務(wù),由于是并行隊(duì)列 ,執(zhí)行這個(gè)同步任務(wù)是不需要前面的異步任務(wù)執(zhí)行完了,就直接開始執(zhí)行,所以也就有了下面的打印信息,通過(guò)上面幾個(gè)問(wèn)題,相信理解了之后,對(duì)于串行隊(duì)列或者并行隊(duì)列添加同步任務(wù)或者異步任務(wù)都有了一個(gè)比較深的理解了,我們?cè)俳又驴偨Y(jié)。
GCD不僅僅這些
關(guān)于GCD的內(nèi)容還有下面這些都是值得我們關(guān)注的,下面我們開始一一說(shuō)一說(shuō):
1、dispatch_barrier_async
dispatch_barrier_async 函數(shù)是我們俗稱的柵欄方法,“柵欄”的意思理解一下字面的,就是把外面和里面阻隔開,這個(gè)函數(shù)的作用就是這樣,把插入的這個(gè)柵欄之前和之后的阻隔開,等前面的執(zhí)行完了就執(zhí)行“柵欄函數(shù)”插入的任務(wù),等柵欄的任務(wù)執(zhí)行結(jié)束了就開始執(zhí)行柵欄后面的任務(wù)。看下面一個(gè)簡(jiǎn)單的Demo就理解了。

從上面就可以看到,我們把0插入到第三個(gè)任務(wù)的位置,它是等前面的兩個(gè)任務(wù)執(zhí)行完了,在去執(zhí)行第三個(gè),要是你覺得這里前兩個(gè)任務(wù)簡(jiǎn)單,執(zhí)行不需要太多的時(shí)間的話,你可以試著把前面兩個(gè)任務(wù)的“任務(wù)量”設(shè)置大一點(diǎn),這樣有助于你更好的理解這個(gè)“柵欄”操作!
2、dispatch_after
dispatch_after 延時(shí)操作
如果某一條任務(wù)你想等多少時(shí)間之后再執(zhí)行的話,你就完全可以使用這個(gè)函數(shù)處理,寫法很簡(jiǎn)單,因?yàn)橐呀?jīng)幫我們封裝好了,看下面這兩行代碼:
- // DISPATCH_TIME_NOW 當(dāng)前時(shí)間開始
- // NSEC_PER_SEC 表示時(shí)間的宏,這個(gè)可以自己上網(wǎng)搜索理解
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"延遲了10秒執(zhí)行");
- });
3、dispatch_apply
dispatch_apply 類似一個(gè)for循環(huán),會(huì)在指定的dispatch queue中運(yùn)行block任務(wù)n次,如果隊(duì)列是并發(fā)隊(duì)列,則會(huì)并發(fā)執(zhí)行block任務(wù),dispatch_apply是一個(gè)同步調(diào)用,block任務(wù)執(zhí)行n次后才返回。 由于它是同步的,要是我們下面這樣寫就會(huì)有出問(wèn)題:

可以看到出問(wèn)題了,但我們要是把它放在串行隊(duì)列或者并行隊(duì)列就會(huì)是下面這樣的情況

4、dispatch_group_t
dispatch_group_t的作用我們先說(shuō)說(shuō),在追加到Dispatch Queue 中的多個(gè)任務(wù)全部結(jié)束之后想要執(zhí)行結(jié)束的處理,這種情況也會(huì)經(jīng)常的出現(xiàn),在只使用一個(gè)Serial Dispatch Queue時(shí),只要將想執(zhí)行的操作全部追加該Serial Dispatch Queue中并且追加在結(jié)束處理就可以實(shí)現(xiàn),但是在使用 Concurrent Dispatch Queue 時(shí)或者同時(shí)使用多個(gè) Dispatch Queue時(shí)候,就比較的復(fù)雜了,在這樣的情況下 Dispatch Group 就可以發(fā)揮它的作用了。看看下面的這段代碼:
- -(void)testDispatch_group_t{
- dispatch_group_t group_t = dispatch_group_create();
- dispatch_queue_t queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_group_async(group_t, queue_t, ^{
- NSLog(@"1--當(dāng)前的線程%@",[NSThread currentThread]);
- });
- dispatch_group_async(group_t, queue_t, ^{
- NSLog(@"2--當(dāng)前的線程%@",[NSThread currentThread]);
- });
- dispatch_group_async(group_t, queue_t, ^{
- NSLog(@"3--當(dāng)前的線程%@",[NSThread currentThread]);
- });
- dispatch_group_async(group_t, queue_t, ^{
- for (int i = 1; i<10; i++) {
- 6
- NSLog(@"4--當(dāng)前的線程%@",[NSThread currentThread]);
- }
- });
- // 當(dāng)前的所有的任務(wù)都執(zhí)行結(jié)束
- dispatch_group_notify(group_t, queue_t, ^{
- NSLog(@"前面的全都執(zhí)行結(jié)束了%@",[NSThread currentThread]);
- });
- }
這段代碼的意圖很明顯,看了下面的打印信息這個(gè)你也就理解它了:

總結(jié): 關(guān)于多線程的最基本的問(wèn)題暫時(shí)先總結(jié)這么多,還有許多的問(wèn)題,自己也在總結(jié)當(dāng)中,比如以下線程鎖等等的問(wèn)題,等總結(jié)到差不多的時(shí)候再分享!























