iOS多線程開發—GCD (一)

GCD是什麼?

做爲一個iOS開發者,不管你是大神仍是像我這樣的菜鳥,每個人應該都不會對多線程開發陌生,即使你沒有據說過pthread,NSThread,NSOperation,但你至少多少據說過或者使用過這樣的代碼編程

dispatch_async(dispatch_get_main_queue,{
  //在這裏搞事情
});
複製代碼

那麼恭喜你,你會GCD! 其實當我第一次使用這個代碼的時候,我並不確切的理解以上這段代碼幹了什麼,我只知道這樣幹不會讓個人界面處於沒有反應的狀態。隨着開發經驗的累積,愈來愈多的使用了有關多線程的知識,所以在這裏把個人一些淺薄的理解記錄下來,幫助本身,也但願可以幫助到其餘須要的人。bash

在這裏咱們先給GCD作個定義吧: 1.GCD是Grand Central Dispatch的縮寫,中文能夠稱爲巨牛X的中央派發。這實際上是蘋果公司爲咱們提供的一種多線程編程API。 2.GCD經過block讓咱們能夠很容易的將要執行的任務放入隊列中,咱們不須要關心任務在哪個線程中執行,這就讓開發者可以更容易的使用多線程技術進行編程而不用實際操做線程。 3.GCD爲咱們提供了建立隊列的方法,而且提供了任務同步和異步執行的方法。咱們所須要關心的只是如何去定義一個任務。網絡

進程,線程,同步,異步,並行,併發,串行?傻傻分不清😂

咱們經常說多線程編程,那麼究竟什麼是多線程編程,咱們爲何要使用多線程編程技術呢? 要搞清這麼多概念咱們首先要簡單的說一下計算機的CPU! 如今的計算機可能是所謂的多核計算機,在一個物理核心之外還會使用軟件技術創造出多個虛擬核心。可是不管是有多少個核心,一個CPU核心在同一時間內只能執行一條無分叉的代碼指令這條無分叉的代碼指令就是咱們常說的線程(後面會給出線程更具體的定義),所以若是想提升計算機的運行效率,咱們除了讓CPU具備更多內核之外,還須要使用多線程的方式,讓同一個內核在多條線程之間作切換以此來提升CPU的使用效率。多線程

1.進程(process)併發

  • 進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程。
  • 每一個進程之間是相互獨立的, 每一個進程均運行在其專用且受保護的內存空間內。
  • 進程是一個具備必定獨立功能的程序關於某次數據集合的一次運行活動,它是操做系統分配資源的基本單元。
  • 進程狀態:進程有三個狀態,就緒,運行和阻塞。就緒狀態其實就是獲取了除cpu外的全部資源,只要處理器分配資源立刻就能夠運行。運行態就是獲取了處理器分配的資源,程序開始執行,阻塞態,當程序條件不夠時,須要等待條件知足時候才能執行,如等待I/O操做的時候,此刻的狀態就叫阻塞態。

2.線程(thread)異步

  • 一個進程要想執行任務,必需要有線程,至少有一條線程。
  • 一個進程的全部任務都是在線程中執行。
  • 每一個應用程序想要跑起來,最少也要有一條線程存在,其實應用程序啓動的時候咱們的系統就會默認幫咱們的應用程序開啓一條線程,這條線程也叫作'主線程',或者'UI線程'。

3.進程和線程的關係async

  • 線程是進程的執行單元,進程的全部任務都在線程中執行!
  • 線程是 CPU 調用的最小單位。
  • 進程是 CPU 分配資源和調度的單位。
  • 一個程序能夠對應過個進程,一個進程中可有多個線程,但至少要有一條線程。
  • 同一個進程內的線程共享進程資源。

有關進程和線程的內容我參考了《iOS開發之多線程編程總結(一)》,這篇文章對於這方面的總結很到位,有興趣的朋友能夠看一下。 下面我談一下我本身對於同步,異步,並行,併發,串行的理解。spa

4.同步(Synchronous) VS. 異步(Asynchronous) 在這裏呢我也不談什麼理論了,就我開發過程當中的理解談一下吧。操作系統

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");
複製代碼

打印結果以下線程

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
複製代碼

從上面的打印結果咱們能夠看出,在hello,task1和hello,task2之間咱們執行了一個同步任務,這個任務被放在了一個串行隊列當中。所以,Hello,task2必需要等待隊列中的任務被執行完畢以後纔會執行。當咱們在hello,task2和hello,task3中執行了異步任務的時候,hello,task3不須要等待隊列中的任務被執行完再執行。 所以咱們能夠這樣認爲,同步任務必須等到任務執行結束以後纔會返回,異步任務不須要等待任務結束能夠當即返回執行下面的代碼。

5.並行(Paralleism) VS. 併發(Concurrency) 其實簡單來講並行和併發都是計算機同時執行多個線程的策略。只是併發是一種"僞同時",前面咱們已經說過,一個CPU內核在同一個時間只能執行一個線程,併發是經過讓CPU內核在多個線程作快速切換來達到讓程序看起來是同時執行的目的。這種上下文切換(context-switch)的速度很是快,以此來達到線程看起來是並行執行的目的。而並行是多條線程在多個CPU內核之間同時執行,以此來達到提升執行效率的目的。

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
複製代碼

從打印結果咱們能夠清晰的看到,全部被添加到串行隊列的任務都是按照添加順序依次執行的,也就是說串行的基本特色是任務按照順序執行。

並行

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
複製代碼

打印結果代表任務的添加順序和執行順序無關,而且在使用了 [NSThread sleepForTimeInterval:1.0];的狀況下,全部人物的執行時間是同樣的,這說明它們是並行執行的,若是你有興趣的話還能夠打印一下它們執行任務的線程,這樣將會得到更清楚的顯示。

GCD出場👏👏👏

隨着一陣啪啪啪(鍵盤的聲音)🙈GCD 出場了。 1,爲何使用GCD? 能夠這樣說,GCD爲咱們提供了一個更加容易的方式來實現多線程編程,咱們不用直接去創建,管理線程,而只須要經過GCD幫助咱們把任務放入相應的隊列中來實現多線程編程的特色。 2,爲何使用多線程編程? 就我如今很少的經驗來講,(1),多線程編程使咱們在編程的過程當中將一下耗時的操做放在非主線程當中,避免了阻塞主線程。(2),在與網絡的交互當中提升了效率,好比說咱們可使用多線程並行上傳和下載來提升速度。 3,多線程編程須要注意什麼? (1),避免死鎖,後面咱們會具體說到。 (2),數據競爭,後面咱們會具體說到。 (3),避免創建大量的無用線程,線程的建立和維護是須要消耗系統資源的,線程的堆棧都須要建立和維護,所以建立大量線程是百害而無一利。所以咱們要記住,多線程技術自己是沒有好處的,關鍵是要使用多線程完成並行操做以此來提升效率。

1.建立隊列

在GCD中有三種不一樣的隊列:

  • 主隊列:這是一個特殊的串行隊列,隊列中的任務都在主線程中執行。
  • 串行隊列:任務在隊列中先進先出,每次執行一個任務。
  • 併發隊列:任務在隊列中也是先進先出,可是同時能夠執行多個任務。
//獲取主隊列
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);
複製代碼

當咱們使用dispatch_queue_create的時候,咱們建立了一個串行隊列或者並行隊列,當咱們使用dispatch_get_main_queue();時咱們獲取了系統建立的主隊列,dispatch_get_global_queue讓咱們獲取了系統中的全局隊列,而且經過參數爲全局隊列設定了優先級。

全局隊列有四種優先級分別用宏定義

#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,串行隊列,並行隊列,同步,異步與線程之間的關係。 2,死鎖問題是如何產生的。

隊列 同步 異步
主隊列 在主線程執行 在主線程執行
串行隊列 在當前線程執行 在新建線程執行
並行隊列 在當前線程執行 在新建線程執行

讓咱們看如下代碼來驗證上述結論:

dispatch_queue_t mainQueue = dispatch_get_main_queue();
 
 //在主線程同步追加任務形成死鎖
  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}
複製代碼

第一個主隊列同步追加任務會形成死鎖,咱們從棧調用能夠看出以上代碼是在主線程執行。第二主隊列異步追加任務能夠順利執行,咱們從打印能夠看出是在主線程執行。

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)}
複製代碼

第一個是在當前線程,由於當前線程是主線程,因此也就是在主線程執行。第二個是新建線程執行。

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)}
複製代碼

第一個是在當前線程,由於當前線程是主線程,因此也就是在主線程執行。第二個是新建線程執行。

經過以上的代碼咱們能夠看出,並行隊列不必定會新建線程,串行隊列也不必定只在當前線程執行。所以,當咱們考慮隊列是在哪一個線程執行的時候咱們必定要考慮它是同步仍是異步執行的問題

下面咱們來看死鎖: 其實對於死鎖我以爲你們記住這句話就夠了,在當前串行隊列中同步追加任務必然形成死鎖。 好比咱們上面用到的在主隊列中同步添加任務,由於dispatch_sync會阻塞當隊列程直到block中追加的任務執行完成以後在繼續執行,可是block中的任務是被添加到主隊列最後的位置,那麼主隊列中其餘任務若是不完成的話追加的block是不會執行的,可是隊列被阻塞,block前面的任務沒法執行,這就形成了在主隊列中任務互相等待的狀況,最終形成死鎖。 在分析死鎖問題是不要過多的考慮使用的是什麼線程,由於咱們在使用GCD的時候首先考慮的是隊列和任務,至於線程的分配和維護是由系統決定的,若是咱們老是考慮線程那樣每每會使咱們難以分析死鎖的緣由。 至於解決死鎖的方法其實也很簡單,使用dispatch_async避免當前隊列被阻塞,這樣咱們就能夠在不等待追加到隊列最後的任務完成以前繼續執行隊列中的任務。並將追加的任務添加到隊列末尾。

參考文章: 《iOS開發之多線程編程總結(一)》 《Objective-C高級編程:iOS與OS X多線程和內存管理》 《Effective Objective-C 2.0》

相關文章
相關標籤/搜索