前言
在開發(fā)過程中,我們會經(jīng)常和網(wǎng)絡(luò)打交道,與網(wǎng)絡(luò)相關(guān)的知識無疑是iOS開發(fā)中的難點之一,學(xué)習(xí)多線程,我走了很多彎路,遇到過很多坑,不過對于我們來說,學(xué)習(xí)就是需要從一個坑爬出來,然后又跳到另一個坑.本篇文章主要介紹多線程相關(guān)的基本知識,想要深入了解,可以關(guān)注CocoaChina微信公眾號,里面有很多大牛的技術(shù)博客.感謝@小笨狼Lc提供的相關(guān)資料.接下來我們正式開始我們的多線程之旅吧.
在正式接觸多線程的核心知識前,我們需要了解一些與網(wǎng)絡(luò)相關(guān)的基本知識.比如說:進(jìn)程和線程分別代表的是什么意思. 兩者有什么聯(lián)系等.
進(jìn)程與線程的基本概念
- 什么是進(jìn)程 : 所謂的進(jìn)程指的就是系統(tǒng)在運行過程中正在運行的一個應(yīng)用程序.
- 進(jìn)程的特點 : iOS系統(tǒng)中進(jìn)程之間是相互獨立的,也就是說,每個進(jìn)程都是在其受保護(hù)的內(nèi)存空間中運行的.這就是為什么iOS系統(tǒng)用起來比某些系統(tǒng)(沒有特指)更加的流暢的原因.下面我們根據(jù)一副圖來說明iOS中進(jìn)程的特點.
在我們的Mac電腦上同時打開快播和迅雷兩款軟件,從圖片中可以看出,他們之間是沒有聯(lián)系的,兩兩之間是相互獨立的.我們可以通過Mac電腦上的"活動監(jiān)視器"來查看所有正在開啟的進(jìn)程
什么是線程 :一個進(jìn)程(后面我們直接就說成應(yīng)用程序)想要執(zhí)行任務(wù),那么它必須要有線程,至少要有一條線程,原因是應(yīng)用程序中的所有的任務(wù)都是在線程上執(zhí)行的,也就是說,沒有線程就不能執(zhí)行任務(wù).
比如根據(jù)下面的圖片,可以看出當(dāng)我們開啟QQ音樂和快播兩款軟件時需要一條對應(yīng)的線程,并且如果我們想要播放音樂或者是下載電影(0),那么他們也要有一條與之對應(yīng)的線程.總的來說,只要應(yīng)用程序想要執(zhí)行某項任務(wù),那么它就必須要有線程,至少要有一條.注意:播放音樂的線程和下載電影的線程之間是相互獨立的.
線程的串行與并行
- 線程串行 : 在同一條線程上執(zhí)行所有的任務(wù),這種運行方式就叫做串行.
- 串行的特點 : 如果一個進(jìn)程執(zhí)行任務(wù)的方式是串行執(zhí)行,那么就表示該進(jìn)程上的所有任務(wù)都是在同一條線程上執(zhí)行的,并且他們的執(zhí)行方式是按順序執(zhí)行的,也就是說,在同時間,一條線程只能執(zhí)行一個任務(wù),只有當(dāng)當(dāng)前任務(wù)執(zhí)行完畢之后,才會去繼續(xù)執(zhí)行下一個任務(wù)(執(zhí)行的任務(wù)是有序的).
- 比如下面的圖片:只有一條線程(黑色箭頭).我們需要下載A, B, C三個文件,這時候的執(zhí)行順序是,只有當(dāng)"A文件下載"完畢之后,才會去執(zhí)行"下載文件B"操作,最后當(dāng)"下載文件B"執(zhí)行完畢之后,才去執(zhí)行"下載文件C".
- 什么是多線程 : 在同一個進(jìn)程中,擁有多條線程,并行執(zhí)行多個任務(wù)(關(guān)于多線程,下面會有更加詳細(xì)的介紹,這里只是其拋磚引玉的作用).
- 什么是并行 : 多條線程同時執(zhí)行多個任務(wù)(執(zhí)行的任務(wù)是無序的)比如說下面的圖片中,開啟了三條線程(黑色箭頭)分別執(zhí)行下載對應(yīng)的文件,就是說:在同一個進(jìn)程中開啟3條線程,同時執(zhí)行下載操作,實現(xiàn)ABC三個文件同時下載.
- 注意 : 并行執(zhí)行的本質(zhì)并不是真正意義上的同時執(zhí)行,那只是給用戶視覺上的錯覺.它的實質(zhì)是在同一時間刻也是只能執(zhí)行一個任務(wù).它只是線程之間高速切換,給人一種同時下載資源的錯覺.(知道就可以了,不必較真兒)
多線程
- 什么是多線程 : 在一個進(jìn)程中可以開啟多條線程,同時執(zhí)行多個任務(wù).
- 使用多線程的優(yōu)缺點
- 1, 多線程的優(yōu)點
- 1.1, 在一定程度上可以提高程序的執(zhí)行效率
- 1.2, 能夠適當(dāng)提高資源的利用率(比如說:CPU, 內(nèi)存等利用率)
- 2, 多線程的缺點
- 2.1, 創(chuàng)建線程是有一定的開銷的,如果開啟大量的線程的話,肯定會降低程序的性能.
- 2.2, 線程開啟的越多,CPU在調(diào)度線程上的開銷就越大
- 2.3, 程序的設(shè)計變得更加復(fù)雜,比如線程間的通信(下面會詳細(xì)介紹),又或者多線程的數(shù)據(jù)共享
iOS中多線程的實現(xiàn)方案
- pthread : 一套通用的多線程并且基于C語言的API,由程序員來管理線程的生命周期,使用難度很大(本篇文章就不介紹了)
- NSThread : 相對于pthread,它更加面向?qū)ο?使用起來相對簡單,生命周期由程序員來管理,但是在開發(fā)中一般不用,使用場景比如說:我想要知道當(dāng)前線程是主線程還是子線程([NSThread currentThread])
- GCD : 是對NSThread進(jìn)一步的封裝,使用起來更加建檔方便快捷,主要是它使用了block代碼塊.是一套基于C的API,生命周期由系統(tǒng)自動管理.
- NSOperation : 是對GCD進(jìn)一步的封裝,底層是GCD,相對GCD來說,使用更加面向?qū)ο?線程的命周期也是由系統(tǒng)自動管理.
GCD
什么是GCD : GCD是Grand Central Disparch的簡稱,GCD可以自動管理線程的生命周期(創(chuàng)建線程, 調(diào)度線程以及銷毀線程).所以我們不需要再去關(guān)注線程,只需要高數(shù)GCD執(zhí)行的是什么任務(wù)以及以什么方式執(zhí)行任務(wù)即可.
GCD的兩個核心概念(任務(wù)和隊列)
- 任務(wù): 所謂的任務(wù)就是我們想要執(zhí)行的代碼,換句話說就是block代碼塊中的代碼又或者是一個指針函數(shù).
- 隊列 : 可以將隊列比喻成存放任務(wù)的容器
GCD的兩個核心步驟:
- 制定任務(wù) : 即想要實現(xiàn)什么效果的代碼
- 添加任務(wù) : 即將確定的任務(wù)添加到隊列中,GCD會自動將添加的任務(wù)取出來,放到對應(yīng)線程上去執(zhí)行.這里涉及到任務(wù)進(jìn)出一個原則:FIFO(先進(jìn)先出)
dispatch_queue_t queue
對象就有內(nèi)存。跟普通OC對象類似拙毫,我們可以用dispatch_retain()和dispatch_release()對其進(jìn)行內(nèi)存管理依许,當(dāng)一個任務(wù)加入到一個queue中的時候,任務(wù)會retain這個queue缀蹄,直到任務(wù)執(zhí)行完成才會release峭跳。
在iOS 6之后,dispatch對象已經(jīng)支持ARC缺前,所以在ARC工程之下蛀醉,我們可以不用擔(dān)心他的內(nèi)存是否被釋放.隊列的知識補(bǔ)充:要申明一個dispatch的屬性。一般情況下我們只需要用strong即可衅码。
@property (nonatomic, strong) dispatch_queue_t queue;
- 注意:如果你是寫一個framework拯刁,framework的使用者的SDK有可能還是古董級的iOS6之前。那么你需要根據(jù)OS_OBJECT_USE_OBJC做一個判斷是使用strong還是assign逝段。
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) dispatch_queue_t queue;
#else
@property (nonatomic, assign) dispatch_queue_t queue;
#endif
GCD中的同步函數(shù)與異步函數(shù)
異步函數(shù)(async) : 可以在新線程上執(zhí)行任務(wù)具備開啟新線程的能力.
// 參數(shù)傳的是一個block
dispatch_async(dispatch_queue_t queue, ^(void)block);
// 參數(shù)傳的是一個指針函數(shù)
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
兩個異步執(zhí)行的API實現(xiàn)的功能是一樣的,都是講任務(wù)提交到指定的隊列中,提交后就立即返回,不用等待任務(wù)的執(zhí)行完畢. 提交之后系統(tǒng)內(nèi)部會對隊列queue進(jìn)行一次retain操作,等到任務(wù)執(zhí)行完畢之后,隊列queue再被release一次.它們之間唯一的區(qū)別是最后一個參數(shù)不同,前者是接收block為參數(shù),后者是接收函數(shù)為參數(shù).
**注意(重點:面試可能會被問到) :多線程方法的block中為什么能使用self,不會造成循環(huán)引用嗎?
**答: 首先肯定是不會有循環(huán)引用的,用不著(__weak typeof(self) weakSelf = self).原因:是當(dāng)使用ispatch_async的時候block會被copy(深拷貝)一次,雖然當(dāng)block執(zhí)行任務(wù)完成之后block會被release,但是此時的系統(tǒng)已經(jīng)擁有了之前拷貝的block了,所以用不著擔(dān)心循環(huán)引用問題,也就是說,block中的self不用將之弱化,直接使用即可(注意:這里只是特例,如果沒有copy那么還是需要將block里面的self變?yōu)閣eakSelf)
異步是一個比較抽象的概念垛玻,簡單的說就是將任務(wù)加入到隊列中之后,立即返回奶躯,不需要等待任務(wù)的執(zhí)行.我們用代碼加深一下對概念的理解
異步函數(shù)示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"異步函數(shù)之前 %@",[NSThread currentThread]);
/**
* 參數(shù) 1: 隊列的名稱 參數(shù) 2: 隊列的類型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行隊列
* DISPATCH_QUEUE_CONCURRENT // 表示并發(fā)隊列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"異步函數(shù)中%@",[NSThread currentThread]);
});
NSLog(@"異步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 21:31:17.349 01 - 多線程[998:85748] 異步函數(shù)之前 <NSThread: 0x7fe800707430>{number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多線程[998:85748] 異步函數(shù)之后 <NSThread: 0x7fe800707430>{number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多線程[998:85848] 異步函數(shù)中<NSThread: 0x7fe80077c100>{number = 2, name = (null)}
- 結(jié)論 : 當(dāng)使用異步函數(shù)時,它只是將任務(wù)放置在隊列中而已,并不需要等待任務(wù)的執(zhí)行,當(dāng)將任務(wù)提交到隊列中時,就會立即返回,根據(jù)打印結(jié)果可以看出異步函數(shù)中的打印是最后才打印的.也就是說,使用異步函數(shù)是不會造成線程死鎖等問題.
同步函數(shù) : 只能在當(dāng)前線程上執(zhí)行任務(wù),不具備開啟子線程的能力.
// 參數(shù)是block
dispatch_sync(dispatch_queue_t queue, ^(void)block);
// 參數(shù)是函數(shù)
dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
兩個方法的作用完全是相同的,都是講任務(wù)提交到queue中,任務(wù)添加到隊列中后,不會立即返回,必須要等待任務(wù)執(zhí)行結(jié)束之后再返回,他們的唯一的區(qū)別就是最后一個接收的參數(shù)不一樣,一個是接收block代碼塊,一個接收的是指針函數(shù).
同步表示任務(wù)加入到隊列中之后不會立即返回帚桩,等待任務(wù)完成再返回。語言的描述比較抽象嘹黔,我們再次用代碼加深一下對概念的理解
同步函數(shù)示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
/**
* 參數(shù) 1: 隊列的名稱 參數(shù) 2: 隊列的類型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行隊列
* DISPATCH_QUEUE_CONCURRENT // 表示并發(fā)隊列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
});
NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 22:23:28.534 01 - 多線程[1037:95068] 同步函數(shù)之前 <NSThread: 0x7f8fd2601920>{number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多線程[1037:95068] 同步函數(shù)中<NSThread: 0x7f8fd2601920>{number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多線程[1037:95068] 同步函數(shù)之后 <NSThread: 0x7f8fd2601920>{number = 1, name = main}
總結(jié) : 根據(jù)打印結(jié)果可以看出,它是按順序執(zhí)行的,當(dāng)同步函數(shù)將任務(wù)添加到隊列中后,不會立即返回,而是等待任務(wù)執(zhí)行完畢之后才返回的,而且全部都是在主線程中執(zhí)行的任務(wù).
先在main queue中執(zhí)行第一個NSLog
dispatch_sync會將block提交到queue中账嚎,等待block的執(zhí)行
queue中block前面的任務(wù)執(zhí)行完成之后,block執(zhí)行
block執(zhí)行完成之后儡蔓,dispatch_sync返回
dispatch_sync之后的代碼執(zhí)行
線程死鎖問題
- 造成死鎖的原因 : 由于dispatch_sync(同步)需要等待block被執(zhí)行郭蕉,這就非常容易發(fā)生死鎖。如果一個串行隊列喂江,使用dispatch_sync提交block到自己隊列中召锈,就會發(fā)生死鎖
死鎖示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
/**
* 參數(shù) 1: 隊列的名稱 參數(shù) 2: 隊列的類型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行隊列
* DISPATCH_QUEUE_CONCURRENT // 表示并發(fā)隊列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"異步函數(shù)中%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
});
});
NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 22:40:33.280 01 - 多線程[1112:100793] 同步函數(shù)之前 <NSThread: 0x7ff17a707510>{number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多線程[1112:100793] 同步函數(shù)之后 <NSThread: 0x7ff17a707510>{number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多線程[1112:100849] 異步函數(shù)中<NSThread: 0x7ff17a501380>{number = 2, name = (null)}
- 總結(jié) :
- 上面的代碼已經(jīng)造成了死鎖現(xiàn)象,原因是同步(dispatch_sync)將任務(wù)添加到隊列中后,仍然需要等待任務(wù)執(zhí)行完畢,前面創(chuàng)建隊列queue時,它的類型是串行隊列,串行隊列的本質(zhì)是一個接一個的執(zhí)行,所以block執(zhí)行任務(wù)也需要等待前面的任務(wù)執(zhí)行完畢,也就是等待dispatch_sync執(zhí)行完畢,兩者相互謙讓,相互等待,導(dǎo)致兩個都沒有執(zhí)行,所以執(zhí)行永遠(yuǎn)沒有執(zhí)行完畢,這就是造成死鎖的本質(zhì)原因.
- 根據(jù)上面打印的結(jié)果可以得出造成死鎖的條件:
- 創(chuàng)建的隊列是串行隊列
- 使用同步(dispatch_sync)將任務(wù)添加到自己的隊列中
- 注意 : 如果queue是并行隊列,或者將任務(wù)加入到其他隊列中开呐,就不會發(fā)生死鎖的現(xiàn)象了烟勋。
寫到這里我們需要總結(jié)一下,否則以后我們只會越來越害怕多線程,害怕去接觸網(wǎng)絡(luò)相關(guān)的知識,做事情不能模棱兩可,既然做了,就要弄明白.
- 上面我們學(xué)習(xí)了多線程執(zhí)行任務(wù)的方法(即同步和異步)和隊列的運行方式(串行和并行)這里會很繞,需要弄清楚他們之間的本質(zhì),才能更好的運用它們.
- 下面主要是通過它們之間的區(qū)別以及其自身本質(zhì)來區(qū)分他們
- 同步和異步的主要區(qū)別: 是否具備開啟子線程的能力
- 同步 : 只能在當(dāng)前線程上執(zhí)行任務(wù),不具備開啟新線程的能力
- 異步 : 可以在新線程上執(zhí)行任務(wù),具備開啟新線程的能力
- 串行和并發(fā)的主要區(qū)別: 任務(wù)的執(zhí)行方式
- 串行 : 只要當(dāng)當(dāng)前任務(wù)執(zhí)行完畢之后,才能執(zhí)行下一個任務(wù),是有序的執(zhí)行任務(wù)
- 并發(fā) : 允許多個任務(wù)(并發(fā))同時執(zhí)行
同步:異步:串行:并發(fā).png
下面我們具體舉例
- 同步函數(shù) + 主隊列(死鎖啦)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 將任務(wù)添加到隊列中
dispatch_sync(mainQueue, ^{
NSLog(@"1我的微博@WilliamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
}
打印結(jié)果
2016-03-22 10:39:17.524 01 - 多線程[655:22430] -------------begin
- 注意 :示例中的全局隊列g(shù)lobalQueue是用來檢測線程是不是死鎖了.
異步函數(shù) + 主隊列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 將任務(wù)添加到隊列中
dispatch_async(mainQueue, ^{
NSLog(@"哎呀!媽呀, 多線程的水很深啊%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印結(jié)果
2016-03-22 10:44:01.294 01 - 多線程[668:23853] -------------begin
2016-03-22 10:44:01.294 01 - 多線程[668:23853] -------------End
2016-03-22 10:44:01.302 01 - 多線程[668:23853] 哎呀!媽呀, 多線程的水很深啊<NSThread: 0x7fe583700bd0>{number = 1, name = main}
- 注意 : 打印結(jié)果表示 : 當(dāng)使用異步函數(shù)時,當(dāng)將block中的任務(wù)添加到隊列中后,就會立即返回,不會等待任務(wù)的執(zhí)行.
同步函數(shù) + 串行隊列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
// 將任務(wù)添加到隊列中
dispatch_sync(syncSerial, ^{
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_sync(syncSerial, ^{
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_sync(syncSerial, ^{
NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印結(jié)果
2016-03-22 10:52:32.478 01 - 多線程[703:27166] -------------begin
2016-03-22 10:52:32.478 01 - 多線程[703:27166] 1WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] 2WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] 3WilliiamAlex大叔<NSThread: 0x7fbb0b6075d0>{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多線程[703:27166] -------------End
- 注意: 從打印結(jié)果中看,同步+串行:當(dāng)將block中的任務(wù)添加到隊列中后,并不會立即返回,而是等待任務(wù)執(zhí)行完畢,并且任務(wù)是一個一個執(zhí)行的,是有序的,不會開啟新的線程.
異步函數(shù) + 串行隊列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
// 將任務(wù)添加到隊列中
dispatch_async(syncSerial, ^{
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_async(syncSerial, ^{
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_async(syncSerial, ^{
NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印結(jié)果
2016-03-22 10:57:45.433 01 - 多線程[716:28932] -------------begin
2016-03-22 10:57:45.434 01 - 多線程[716:28932] -------------End
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 1WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 2WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多線程[716:28983] 3WilliiamAlex大叔<NSThread: 0x7f7ff0c20060>{number = 2, name = (null)}
- 注意 : 異步 + 串行 : 是有序執(zhí)行的,開啟了新的線程,注意,只要是異步函數(shù),當(dāng)將block中的惹怒添加到隊列中后會立即返回,不會等待任務(wù)的執(zhí)行.
同步函數(shù) + 并發(fā)隊列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取并發(fā)隊列 : DISPATCH_QUEUE_CONCURRENT
// dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
// 可以直接使用全局的并發(fā)隊列(第一個0:表示系統(tǒng)默認(rèn)即可. 第二個0:表示預(yù)留字段)
dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);
// 將任務(wù)添加到隊列中
dispatch_sync(globalQuque, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
dispatch_sync(globalQuque, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
NSLog(@"-------------End");
}
打印結(jié)果
2016-03-22 11:13:23.988 01 - 多線程[794:35399] -------------begin
2016-03-22 11:13:23.989 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.989 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 1WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] 2WilliiamAlex大叔<NSThread: 0x7fafea506970>{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多線程[794:35399] -------------End
- 注意 : 同步 + 并發(fā):都是在主線程上執(zhí)行的,并且執(zhí)行方式是只有當(dāng)前面的所有任務(wù)有序低執(zhí)行完畢之后,才會執(zhí)行下一個任務(wù),不具備開啟新線程的能力.
異步函數(shù) + 并發(fā)隊列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 獲取并發(fā)隊列 : DISPATCH_QUEUE_CONCURRENT
// dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
// 可以直接使用全局的并發(fā)隊列(第一個0:表示系統(tǒng)默認(rèn)即可. 第二個0:表示預(yù)留字段)
dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);
// 將任務(wù)添加到隊列中
dispatch_async(globalQuque, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
dispatch_async(globalQuque, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
NSLog(@"-------------End");
}
打印結(jié)果(部分打印)
2016-03-22 11:09:43.346 01 - 多線程[774:33864] -------------begin
2016-03-22 11:09:43.347 01 - 多線程[774:33864] -------------End
2016-03-22 11:09:43.347 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread: 0x7fa45b43d030>{number = 2, name = (null)}
2016-03-22 11:09:43.347 01 - 多線程[774:33907] 1WilliiamAlex大叔<NSThread: 0x7fa45b61fcf0>{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread: 0x7fa45b43d030>{number = 2, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33907] 1WilliiamAlex大叔<NSThread: 0x7fa45b61fcf0>{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多線程[774:33908] 2WilliiamAlex大叔<NSThread:
- 注意 : 異步 + 并發(fā):開啟了子線程,執(zhí)行順序是亂序的,并且將任務(wù)添加到隊列中后就立即返回了,不用等待任務(wù)的執(zhí)行.
- 補(bǔ)充
- GCD中還提供了一個函數(shù)來執(zhí)行任務(wù),但是它有點特殊.
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
- 該函數(shù)的使用場景 : 有時候我們需要讓某個任務(wù)單獨執(zhí)行,也就是說在執(zhí)行的時候,不允許其他的任務(wù)執(zhí)行.這時候我們就可以使用dispatch_barrier.
dispatch_barrier示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函數(shù)之前 %@",[NSThread currentThread]);
/**
* 參數(shù) 1: 隊列的名稱 參數(shù) 2: 隊列的類型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行隊列
* DISPATCH_QUEUE_CONCURRENT // 表示并發(fā)隊列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"同步函數(shù)中%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"猜猜我在哪里執(zhí)行%@",[NSThread currentThread]);
});
NSLog(@"同步函數(shù)之后 %@",[NSThread currentThread]);
}
打印結(jié)果
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)之前 <NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)中<NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多線程[1196:116016] 同步函數(shù)之后 <NSThread: 0x7ffea8c02a00>{number = 1, name = main}
2016-03-21 23:44:55.518 01 - 多線程[1196:116084] 猜猜我在哪里執(zhí)行<NSThread: 0x7ffea8e5ba80>{number = 2, name = (null)}
- 從打印結(jié)果中獲取到的信息 : 當(dāng)我們使用dispatch_barrier將任務(wù)添加到隊列中,隊列中的任務(wù)會在前面所有的任務(wù)執(zhí)行完畢后執(zhí)行,當(dāng)dispatch_barrier執(zhí)行任務(wù)過程中,其它任務(wù)是不允許執(zhí)行的,直到barrier任務(wù)執(zhí)行完成
- 還有一個有趣的信息,從打印出來的結(jié)果中可以看到dispatch_barrier上執(zhí)行的任務(wù)都是在子線程上執(zhí)行的.所以,dispatch_barrier是將任務(wù)添加到并發(fā)隊列中的.
知識拓展
- dispatch_barrier最典型的使用場景是讀寫問題,NSMutableDictionary在多個線程中如果同時寫入筐付,或者一個線程寫入一個線程讀取卵惦,會發(fā)生無法預(yù)料的錯誤。但是他可以在多個線程中同時讀取瓦戚。如果多個線程同時使用同一個NSMutableDictionary沮尿。怎樣才能保護(hù)NSMutableDictionary不發(fā)生意外呢?
- (void)setObject:(id)anObject forKey:(id
)aKey
{
dispatch_barrier_async(self.concurrentQueue, ^{
[self.mutableDictionary setObject:anObject forKey:aKey];
});
}
- (id)objectForKey:(id)aKey
{
__block id object = nil;
dispatch_sync(self.concurrentQueue, ^{
object = [self.mutableDictionary objectForKey:aKey];
}); return object;
}
當(dāng)NSMutableDictionary寫入的時候较解,我們使用dispatch_barrier_async畜疾,讓其單獨執(zhí)行寫入操作,不允許其他寫入操作或者讀取操作同時執(zhí)行印衔。當(dāng)讀取的時候啡捶,我們只需要直接使用dispatch_sync,讓其正常讀取即可奸焙。這樣就可以保證寫入時不被打擾瞎暑,讀取時可以多個線程同時進(jìn)行
總結(jié) : 使用dispatch_barrier的前提是,隊列必須是并發(fā)隊列,但是這個queue(隊列)不能是全局隊列.
dispatch_barrier的最根本的原理 : 只有它前面所有的任務(wù)都執(zhí)行完畢之后才會執(zhí)行dispatch_barrier中隊列的任務(wù),并且在執(zhí)行任務(wù)過程中,其它任務(wù)是不允許執(zhí)行的,也就是說,當(dāng)它在執(zhí)行過程中,只有它在執(zhí)行.
創(chuàng)建隊列
- 創(chuàng)建隊列 : 使用dispatch_queue_create函數(shù)創(chuàng)建的隊列.
/*
參數(shù) 1: 隊列的名稱
參數(shù) 2: 隊列的類型
* NULL // 表示串行隊列
* DISPATCH_QUEUE_SERIAL // 表示串行隊列
* DISPATCH_QUEUE_CONCURRENT // 表示并發(fā)隊列
*/
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
- 串行隊列
使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
// 創(chuàng)建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
// 方式 1 : NULL
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", NULL);
// 方式 2 : DISPATCH_QUEUE_SERIAL
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_SERIAL);
使用主隊列(跟主線程相關(guān)聯(lián)的隊列)
主隊列是GCD自帶的一種特殊的串行隊列
放在主隊列中的任務(wù),都會放到主線程中執(zhí)行
使用dispatch_get_main_queue()獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
- 并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_CONCURRENT);
- 全局并發(fā)隊列
GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊列与帆,供整個應(yīng)用使用了赌,可以無需手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優(yōu)先級
unsigned long flags); // 用0即可
獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
知識拓展
- 全局并發(fā)隊列函數(shù)中的兩個參數(shù):
- identifier: 用以標(biāo)識隊列優(yōu)先級,推薦用qos_class枚舉作為參數(shù)玄糟,也可以使用dispatch_queue_priority_t
- flags: 預(yù)留字段勿她,傳入任何非0的值都可能導(dǎo)致返回NULL.
- 獲取主隊列
// 直接獲取主隊列
NSLog(@"主隊列上的任務(wù)都是在主線程上執(zhí)行的%@",dispatch_get_main_queue());
主隊列的使用場景
- 主線程是我們最常用的線程,GCD提供了非常簡單的獲取主線程隊列的方法.我們使用主隊列或者是主線程主要是執(zhí)行一些UI界面的操作(比如:UI界面的屬性等事件),記住:只要是一些耗時的操作一般都是放在子線程上執(zhí)行,不耗時的操作就放在主線程上執(zhí)行.
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI界面操作
});
執(zhí)行加入到主線程隊列的block阵翎,App會調(diào)用dispatch_main(), NSApplicationMain()逢并,或者在主線程使用CFRunLoop。
有的朋友習(xí)慣將參數(shù)都設(shè)置為0
很多時候我們喜歡將0或者NULL傳入作為參數(shù)
dispatch_get_global_queue(NULL, NULL)
由于NULL等于0郭卫,也就是DISPATCH_QUEUE_PRIORITY_DEFAULT筒狠,所以返回的是默認(rèn)優(yōu)先級
網(wǎng)絡(luò)延遲
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);
- 幾種延遲操作
- 定時器(NSTimer)
- GCD(dispatch_after)
- 調(diào)用NSObject方法(performeSelector)
- NSTimer定時器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(run) userInfo:nil repeats:YES];
- performeSelector
// 直接調(diào)用NSObject方法,,performeSelector方法也是比較常見具體實現(xiàn)是.
// nonnull : 表示參數(shù)不能設(shè)置為空
// nullable : 表示參數(shù)可以設(shè)置為空
[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval)]
[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval) inModes:(nonnull NSArray<NSString *> *)]
- GCD(dispatch_after)
/*
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)
注意 : 不能傳DISPATCH_TIME_FOREVER,會一直阻塞線程
*/
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0
});
總結(jié)延遲操作
函數(shù)說明: 2.0秒之后block中的任務(wù)會加到隊列中,兩個函數(shù)的第一個參數(shù)都是when,表示時間,當(dāng)我們傳入DISPATCH_TIME_NOW當(dāng)前函數(shù)就相當(dāng)于是一個dispatch_async(異步函數(shù)).值得注意的是,我們不能傳入DISPATCH_TIME_FOREVER,因為這樣會造成線程的阻塞.
參數(shù)說明:dispatch_after接收block作為參數(shù),系統(tǒng)持有block箱沦,block中self不需要weak辩恼。dispatch_after_f接收work函數(shù)作為參數(shù),context作為work函數(shù)的第一個參數(shù)
注意: 需要注意的是這里的延時并不精確的,因為加入隊列不一定會立即執(zhí)行旺订。延時1s可能會1.5s甚至2s之后才會執(zhí)行向抢。
dispatch_queue_set_specific 和 dispatch_queue_get_specific
dispatch_queue_set_specific的使用場景
- 當(dāng)我們需要將某些東西關(guān)聯(lián)在隊列上,比如說我們想在隊列上存儲一些東西,又或者我們想?yún)^(qū)分兩個隊列。GCD提供了dispatch_queue_set_specific方法聘萨,通過key,將context關(guān)聯(lián)到queue上
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
參數(shù)說明
- queue:需要關(guān)聯(lián)的queue童太,不允許傳入nil
- key:隊列的標(biāo)識
- context:要關(guān)聯(lián)的內(nèi)容米辐,可以為nil
- destructor:釋放context的函數(shù)胸完,當(dāng)新的context被設(shè)置時,destructor會被調(diào)用
dispatch_queue_get_specific的使用場景
- 有存就有取翘贮,將context關(guān)聯(lián)到queue上之后赊窥,可以通過dispatch_queue_get_specific或者dispatch_get_specific方法將值取出來。
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);
dispatch_queue_get_specific的解釋
根據(jù)queue和key取出context狸页,queue參數(shù)不能傳入全局隊列
dispatch_get_specific: 根據(jù)唯一的key取出當(dāng)前queue的context锨能。如果當(dāng)前queue沒有key對應(yīng)的context,則去queue的target queue取芍耘,取不著返回nil.
如果對全局隊列取址遇,也會返回nil
iOS 6之后dispatch_get_current_queue()被廢棄,如果我們需要區(qū)分不同的queue斋竞,可以使用set_specific方法倔约。根據(jù)對應(yīng)的key是否有值來區(qū)分
一個人的力量是有限的,如果文章有有錯誤,希望大家指出來,相互幫助,相互進(jìn)步.感謝@小笨狼Lc提供資料,想要了解更多知識可以點進(jìn)連接,關(guān)注CocoaChina的微信公眾號.http://www.cocoachina.com/ios/20160225/15422.html
后續(xù)會持續(xù)更新.....加油!!!!!