GCD是什么?
作為一個iOS開發(fā)者上真,無論你是大神還是像我這樣的菜鳥,每一個人應(yīng)該都不會對多線程開發(fā)陌生,即便你沒有聽說過pthread,NSThread,NSOperation,但你至少多少聽說過或者使用過這樣的代碼
dispatch_async(dispatch_get_main_queue,{
//在這里搞事情
});
那么恭喜你乍楚,你會GCD!
其實當(dāng)我第一次使用這個代碼的時候届慈,我并不確切的理解以上這段代碼干了什么徒溪,我只知道這樣干不會讓我的界面處于沒有反應(yīng)的狀態(tài)忿偷。隨著開發(fā)經(jīng)驗的累積,越來越多的使用了有關(guān)多線程的知識臊泌,因此在這里把我的一些淺薄的理解記錄下來鲤桥,幫助自己,也希望能夠幫助到其他需要的人渠概。
在這里我們先給GCD做個定義吧:
1.GCD是Grand Central Dispatch中文可以稱為巨牛X的中央派發(fā)茶凳。這其實是蘋果公司為我們提供的一種多線程編程API。
2.GCD通過block讓我們可以很容易的將要執(zhí)行的任務(wù)放入隊列中播揪,我們不需要關(guān)心任務(wù)在哪一個線程中執(zhí)行贮喧,這就讓開發(fā)者能夠更容易的使用多線程技術(shù)進行編程而不用實際操作線程。
3.GCD為我們提供了創(chuàng)建隊列的方法剪芍,并且提供了任務(wù)同步和異步執(zhí)行的方法塞淹。我們所需要關(guān)心的只是如何去定義一個任務(wù)。
進程罪裹,線程饱普,同步,異步状共,并行套耕,并發(fā),串行峡继?傻傻分不清??
我們常常說多線程編程冯袍,那么究竟什么是多線程編程,我們?yōu)槭裁匆褂枚嗑€程編程技術(shù)呢碾牌?
要搞清這么多概念我們首先要簡單的說一下計算機的CPU康愤!
現(xiàn)在的計算機多是所謂的多核計算機,在一個物理核心以外還會使用軟件技術(shù)創(chuàng)造出多個虛擬核心舶吗。但是無論是有多少個核心征冷,一個CPU核心在同一時間內(nèi)只能執(zhí)行一條無分叉的代碼指令這條無分叉的代碼指令就是我們常說的線程(后面會給出線程更具體的定義),因此如果想提高計算機的運行效率誓琼,我們除了讓CPU具有更多內(nèi)核以外检激,還需要使用多線程的方式,讓同一個內(nèi)核在多條線程之間做切換以此來提高CPU的使用效率腹侣。
1.進程(process)
- 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序叔收,就是一段程序的執(zhí)行過程。
- 每個進程之間是相互獨立的, 每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)傲隶。
- 進程是一個具有一定獨立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運行活動饺律,它是操作系統(tǒng)分配資源的基本單元。
- 進程狀態(tài):進程有三個狀態(tài)伦籍,就緒蓝晒,運行和阻塞腮出。就緒狀態(tài)其實就是獲取了除cpu外的所有資源,只要處理器分配資源馬上就可以運行芝薇。運行態(tài)就是獲取了處理器分配的資源胚嘲,程序開始執(zhí)行,阻塞態(tài)洛二,當(dāng)程序條件不夠時馋劈,需要等待條件滿足時候才能執(zhí)行,如等待I/O操作的時候晾嘶,此刻的狀態(tài)就叫阻塞態(tài)妓雾。
2.線程(thread)
- 一個進程要想執(zhí)行任務(wù),必須要有線程,至少有一條線程。
- 一個進程的所有任務(wù)都是在線程中執(zhí)行垒迂。
- 每個應(yīng)用程序想要跑起來,最少也要有一條線程存在,其實應(yīng)用程序啟動的時候我們的系統(tǒng)就會默認幫我們的應(yīng)用程序開啟一條線程,這條線程也叫做'主線程',或者'UI線程'械姻。
3.進程和線程的關(guān)系
- 線程是進程的執(zhí)行單元,進程的所有任務(wù)都在線程中執(zhí)行机断!
- 線程是 CPU 調(diào)用的最小單位楷拳。
- 進程是 CPU 分配資源和調(diào)度的單位。
- 一個程序可以對應(yīng)過個進程,一個進程中可有多個線程,但至少要有一條線程吏奸。
- 同一個進程內(nèi)的線程共享進程資源欢揖。
有關(guān)進程和線程的內(nèi)容我參考了《iOS開發(fā)之多線程編程總結(jié)(一)》,這篇文章對于這方面的總結(jié)很到位奋蔚,有興趣的朋友可以看一下她混。
下面我談一下我自己對于同步,異步泊碑,并行坤按,并發(fā),串行的理解馒过。
4.同步(Synchronous) VS. 異步(Asynchronous)
說到同步我想到了我曾今實習(xí)丟臉的事情晋涣,帶我的老程序員問我什么是同步,結(jié)果我把同步理解成了并行沉桌。還真是丟人啊,這也說明了學(xué)藝不精總有露餡的時候算吩。
在這里呢我也不談什么理論了留凭,就我開發(fā)過程中的理解談一下吧。
NSLog(@"Hellot, task1");
dispatch_queue_t queue = dispatch_queue_create("Sereal_Queue_1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 10 ; i ++) {
NSLog(@"%d",i);
}
});
NSLog(@"Hello, taks2");
dispatch_async(queue, ^{
for (int i = 0; i < 10 ; i ++) {
NSLog(@"%d",i);
}
});
NSLog(@"Hello,task3");
打印結(jié)果如下
2017-07-01 20:40:56.914 GCD_Test[85651:1471470] Hellot, task1
2017-07-01 20:40:56.914 GCD_Test[85651:1471470] 0
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 1
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 2
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 3
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 4
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 5
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 6
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 7
2017-07-01 20:40:56.915 GCD_Test[85651:1471470] 8
2017-07-01 20:40:56.916 GCD_Test[85651:1471470] 9
2017-07-01 20:40:56.916 GCD_Test[85651:1471470] Hello, taks2
2017-07-01 20:40:56.916 GCD_Test[85651:1471470] Hello,task3
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 0
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 1
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 2
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 3
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 4
2017-07-01 20:40:56.916 GCD_Test[85651:1471599] 5
2017-07-01 20:40:56.917 GCD_Test[85651:1471599] 6
2017-07-01 20:40:56.917 GCD_Test[85651:1471599] 7
2017-07-01 20:40:56.917 GCD_Test[85651:1471599] 8
2017-07-01 20:40:56.922 GCD_Test[85651:1471599] 9
從上面的打印結(jié)果我們可以看出偎巢,在hello,task1和hello,task2之間我們執(zhí)行了一個同步任務(wù)蔼夜,這個任務(wù)被放在了一個串行隊列當(dāng)中。因此压昼,Hello,task2必須要等待隊列中的任務(wù)被執(zhí)行完畢之后才會執(zhí)行求冷。當(dāng)我們在hello瘤运,task2和hello,task3中執(zhí)行了異步任務(wù)的時候,hello,task3不需要等待隊列中的任務(wù)被執(zhí)行完在執(zhí)行匠题。
因此我們可以這樣認為拯坟,同步任務(wù)必須等到任務(wù)執(zhí)行結(jié)束之后才會返回,異步任務(wù)不需要等待任務(wù)結(jié)束可以立即返回執(zhí)行下面的代碼韭山。
5.并行(Paralleism) VS. 并發(fā)(Concurrency)
其實簡單來說并行和并發(fā)都是計算機同時執(zhí)行多個線程的策略郁季。只是并發(fā)是一種"偽同時",前面我們已經(jīng)說過钱磅,一個CPU內(nèi)核在同一個時間只能執(zhí)行一個線程梦裂,并發(fā)是通過讓CPU內(nèi)核在多個線程做快速切換來達到讓程序看起來是同時執(zhí)行的目的。這種上下文切換(context-switch)的速度非掣堑快年柠,以此來達到線程看起來是并行執(zhí)行的目的。而并行是多條線程在多個CPU內(nèi)核之間同時執(zhí)行褪迟,以此來達到提高執(zhí)行效率的目的冗恨。
6.并行(Paralleism) VS. 串行(Serial)
從下面的代碼我們先來看一下串行
dispatch_queue_t queue = dispatch_queue_create("Sereal_Queue_1", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10 ; i ++) {
dispatch_async(queue, ^{
NSLog(@"%d",i);
});
}
2017-07-02 01:59:03.214 GCD_Test[86301:1702080] 0
2017-07-02 01:59:03.214 GCD_Test[86301:1702080] 1
2017-07-02 01:59:03.214 GCD_Test[86301:1702080] 2
2017-07-02 01:59:03.214 GCD_Test[86301:1702080] 3
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 4
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 5
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 6
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 7
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 8
2017-07-02 01:59:03.215 GCD_Test[86301:1702080] 9
從打印結(jié)果我們可以清晰的看到,所有被添加到串行隊列的任務(wù)都是按照添加順序依次執(zhí)行的牵咙,也就是說串行的基本特點是任務(wù)按照順序執(zhí)行派近。
并行
dispatch_queue_t concurrentQueue = dispatch_queue_create("Global_Queue_1", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0 ; i < 10 ; i ++) {
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%d",i);
});
}
2017-07-02 02:02:29.759 GCD_Test[86327:1705240] 2
2017-07-02 02:02:29.759 GCD_Test[86327:1705243] 1
2017-07-02 02:02:29.759 GCD_Test[86327:1705239] 0
2017-07-02 02:02:29.759 GCD_Test[86327:1705268] 5
2017-07-02 02:02:29.759 GCD_Test[86327:1705264] 3
2017-07-02 02:02:29.759 GCD_Test[86327:1705267] 4
2017-07-02 02:02:29.759 GCD_Test[86327:1705270] 7
2017-07-02 02:02:29.759 GCD_Test[86327:1705269] 6
2017-07-02 02:02:29.759 GCD_Test[86327:1705271] 8
2017-07-02 02:02:29.759 GCD_Test[86327:1705273] 9
打印結(jié)果表面任務(wù)的添加順序和執(zhí)行順序無關(guān),并且在使用了 [NSThread sleepForTimeInterval:1.0];的情況下洁桌,所有人物的執(zhí)行時間是一樣的渴丸,這說明它們是并行執(zhí)行的,如果你有興趣的話還可以打印一下它們執(zhí)行任務(wù)的線程另凌,這樣將會獲得更清楚的顯示谱轨。
GCD出場??????
隨著一陣啪啪啪(鍵盤的聲音)??GCD 出場了。
1吠谢,為什么使用GCD土童?
可以這樣說,GCD為我們提供了一個更加容易的方式來實現(xiàn)多線程編程工坊,我們不用直接去建立献汗,管理線程,而只需要通過GCD幫助我們把任務(wù)放入相應(yīng)的隊列中來實現(xiàn)多線程編程的特點王污。
2罢吃,為什么使用多線程編程?
就我現(xiàn)在不多的經(jīng)驗來說昭齐,(1),多線程編程使我們在編程的過程中將一下耗時的操作放在非主線程當(dāng)中尿招,避免了阻塞主線程。(2),在與網(wǎng)絡(luò)的交互當(dāng)中提高了效率,比如說我們可以使用多線程并行上傳和下載來提高速度就谜。
3怪蔑,多線程編程需要注意什么?
(1),避免死鎖丧荐,后面我們會具體說到缆瓣。
(2),數(shù)據(jù)競爭,后面我們會具體說到篮奄。
(3),避免建立大量的無用線程捆愁,線程的創(chuàng)建和維護是需要消耗系統(tǒng)資源的,線程的堆棧都需要創(chuàng)建和維護窟却,因此創(chuàng)建大量線程是百害而無一利昼丑。因此我們要記住,多線程技術(shù)本身是沒有好處的夸赫,關(guān)鍵是要使用多線程完成并行操作以此來提高效率菩帝。
1.創(chuàng)建隊列
在GCD中有三種不同的隊列:
- 主隊列:這是一個特殊的串行隊列,隊列中的任務(wù)都在主線程中執(zhí)行茬腿。
- 串行隊列:任務(wù)在隊列中先進先出呼奢,每次執(zhí)行一個任務(wù)。
- 并發(fā)隊列:任務(wù)在隊列中也是先進先出切平,但是同時可以執(zhí)行多個任務(wù)握础。
//獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//獲取串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.jiaxiang.serialQueue", DISPATCH_QUEUE_SERIAL);
//獲取并行隊列
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("com.jiaxiang.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
當(dāng)我們使用dispatch_queue_create的時候,我們創(chuàng)建了一個串行隊列或者并行隊列悴品,當(dāng)我們使用dispatch_get_main_queue();時我們獲取了系統(tǒng)創(chuàng)建的主隊列禀综,dispatch_get_global_queue讓我們獲取了系統(tǒng)中的全局隊列,并且通過參數(shù)為全局隊列設(shè)定了優(yōu)先級苔严。
全局隊列有四種優(yōu)先級分別用宏定義
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
在這里要說兩個問題:
1定枷,串行隊列,并行隊列届氢,同步欠窒,異步與線程之間的關(guān)系。
2退子,死鎖問題是如何產(chǎn)生的岖妄。
隊列 | 同步 | 異步 |
---|---|---|
主隊列 | 在主線程執(zhí)行 | 在主線程執(zhí)行 |
串行隊列 | 在當(dāng)前線程執(zhí)行 | 在新建線程執(zhí)行 |
并行隊列 | 在當(dāng)前線程執(zhí)行 | 在新建線程執(zhí)行 |
讓我們看以下代碼來驗證上述結(jié)論:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//在主線程同步追加任務(wù)造成死鎖
dispatch_sync(mainQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
2017-07-02 14:06:40.371 GCD_Test[86684:1876563] <NSThread: 0x600000079000>{number = 1, name = main}
第一個主隊列同步追加任務(wù)會造成死鎖,我們從棧調(diào)用可以看出以上代碼是在主線程執(zhí)行寂祥。第二主隊列異步追加任務(wù)可以順利執(zhí)行衣吠,我們從打印可以看出是在主線程執(zhí)行。
dispatch_queue_t serialQueue = dispatch_queue_create("com.jiaxiang.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
2017-07-02 14:16:58.578 GCD_Test[86721:1886346] <NSThread: 0x60000006f1c0>{number = 1, name = main}
2017-07-02 14:16:58.579 GCD_Test[86721:1886393] <NSThread: 0x608000074c00>{number = 3, name = (null)}
第一個是在當(dāng)前線程壤靶,因為當(dāng)前線程是主線程,所以也就是在主線程執(zhí)行惊搏。第二個是新建線程執(zhí)行贮乳。
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("com.jiaxiang.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue1, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue1, ^{
NSLog(@"%@",[NSThread currentThread]);
});
2017-07-02 14:20:26.646 GCD_Test[86748:1890613] <NSThread: 0x60800007d300>{number = 1, name = main}
2017-07-02 14:20:26.646 GCD_Test[86748:1890662] <NSThread: 0x6080002675c0>{number = 3, name = (null)}
第一個是在當(dāng)前線程忧换,因為當(dāng)前線程是主線程,所以也就是在主線程執(zhí)行向拆。第二個是新建線程執(zhí)行亚茬。
通過以上的代碼我們可以看出,并行隊列不一定會新建線程浓恳,串行隊列也不一定只在當(dāng)前線程執(zhí)行刹缝。因此,當(dāng)我們考慮隊列是在哪個線程執(zhí)行的時候我們一定要考慮它是同步還是異步執(zhí)行的問題颈将。
下面我們來看死鎖:
其實對于死鎖我覺得大家記住這句話就夠了梢夯,在當(dāng)前串行隊列中同步追加任務(wù)必然造成死鎖。
比如我們上面用到的在主隊列中同步添加任務(wù)晴圾,因為dispatch_sync會阻塞當(dāng)隊列程直到block中追加的任務(wù)執(zhí)行完成之后在繼續(xù)執(zhí)行颂砸,但是block中的任務(wù)是被添加到主隊列最后的位置,那么主隊列中其他任務(wù)如果不完成的話追加的block是不會執(zhí)行的死姚,但是隊列被阻塞人乓,block前面的任務(wù)無法執(zhí)行,這就造成了在主隊列中任務(wù)互相等待的情況都毒,最終造成死鎖色罚。
在分析死鎖問題是不要過多的考慮使用的是什么線程,因為我們在使用GCD的時候首先考慮的是隊列和任務(wù)账劲,至于線程的分配和維護是由系統(tǒng)決定的戳护,如果我們總是考慮線程那樣往往會使我們難以分析死鎖的原因。
至于解決死鎖的方法其實也很簡單涤垫,使用dispatch_async避免當(dāng)前隊列被阻塞姑尺,這樣我們就可以在不等待追加到隊列最后的任務(wù)完成之前繼續(xù)執(zhí)行隊列中的任務(wù)。并將追加的任務(wù)添加到隊列末尾蝠猬。
這篇文章寫到這里先告一段落切蟋,在這里我們主要關(guān)注了多線程的一些理論,還有簡單的GCD使用榆芦,以及在使用多線程中需要注意幾個問題柄粹。下一篇文章我講著重討論一下GCD的幾個高級用法,數(shù)據(jù)競爭匆绣。最后的一篇文章我準(zhǔn)備寫一個簡單的多線程下載和上傳的Demo驻右。
因本人經(jīng)驗有限,文章中難免有錯誤和疏漏崎淳,如果大家發(fā)現(xiàn)了也希望能夠指出堪夭,共同進步!最后如果你覺得這篇文章對你有幫助請點個贊??哦!
參考文章:
《iOS開發(fā)之多線程編程總結(jié)(一)》
《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
《Effective Objective-C 2.0》