iOS底層學習 - 多線程之GCD初探

經過上一個章節,咱們已經瞭解了一些多線程的基本知識。本章就來說解在平時開發中,最爲經常使用的GCD的一些知識git

系列文章傳送門:程序員

iOS底層學習 - 多線程之基礎原理篇github

GCD簡介

定義

GCD全程爲Grand Central Dispatch,由C語言實現,是蘋果爲多核的並行運算提出的解決方案,CGD會自動利用更多的CPU內核,自動管理線程的生命週期,程序員只須要告訴GCD須要執行的任務,無需編寫任何管理線程的代碼。GCD也是iOS使用頻率最高的多線程技術。swift

優點

  • GCD 是蘋果公司爲多核的並行運算提出的解決方案
  • GCD 會自動利用更多的CPU內核(好比雙核、四核)
  • GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
  • 程序員只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼

GCD使用

GCD的使用API較爲簡單,主要分爲同步和異步執行。在上一章咱們對概念已經有所瞭解,就不作贅述了。markdown

同步執行API多線程

//queue:隊列 block:任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
複製代碼

異步執行API併發

//queue:隊列 block:任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
複製代碼

咱們發如今使用GCD時,須要傳入兩個參數。分別是dispatch_queue_t表明添加的隊列類型,dispatch_block_t爲須要執行的任務,下面咱們來看一下不一樣參數的運行效果。app

dispatch_block_t任務

經過查看其定義可得,該參數其實就是一個沒有參數的block回調,用來執行任務的。異步

typedef void (^dispatch_block_t)(void);
複製代碼

dispatch_queue_t隊列

dispatch_queue_t參數表示隊列的類型。根據上一章節咱們知道,隊列(FIFO)在iOS中主要一下分爲4種:async

  1. 主隊列(main_queue):由系統建立的串行隊列。
    • 獲取方式:dispatch_get_main_queue()
  2. 全局隊列(global_queue):由系統建立的併發隊列
    • 獲取方式:dispatch_get_global_queue(long identifier, unsigned long flags);
  3. 串行隊列(Serial Dispatch Queue):自定義的串行隊列
    • 獲取方式:dispatch_queue_create(@"隊列名",DISPATCH_QUEUE_SERIAL)
  4. 併發隊列(Concurrent Dispatch Queue):自定義的併發隊列
    • 獲取方式:dispatch_queue_create(@"隊列名",DISPATCH_QUEUE_CONCURRENT);

其中,全局隊列根據入參的不一樣,會獲取到不一樣的隊列,在平常的開發中,咱們通常入參0,來獲取到主併發隊列。

自定義的隊列都是調用dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)方法來進行建立,兩個參數分別爲自定義隊列名稱隊列類型。其中串行隊列DISPATCH_QUEUE_SERIAL爲宏定義的NULL,因此傳NULL也表示爲串行隊列。

如今,咱們來看這2種執行模式,3種隊列是相互搭配是什麼效果

同步 +(主隊列或者自定義串行隊列)

相關代碼以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任務2",[NSThread currentThread]);
    });
    NSLog(@"任務3:%@",[NSThread currentThread]);
}
複製代碼

運行結果

打印出任務1後,程序死鎖崩潰

爲何會形成以上的結果呢?

首先分析一下代碼:主線程執行完任務1後,在主隊列dispatch_get_main_queue()中同步執行(dispatch_sync)任務2,而後執行任務3。

隊列的特色是FIFO,主隊列中已經存在任務viewDidLoad,往主隊列加入任務2,就須要執行完viewDidLoad才能執行任務2。可是想要執行完viewDidLoad又必須先執行viewDidLoad內的任務2和任務3。這就形成了死鎖

異步 +(主隊列或者自定義串行隊列)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"任務2:%@",[NSThread currentThread]);
    });
    NSLog(@"任務3:%@",[NSThread currentThread]);
}
複製代碼

運行結果

咱們能夠看到當獲取到主隊列後,使用異步執行的方式,不會形成程序的崩潰。

由於主線程執行任務1以後,須要異步(dispatch_async)執行任務2;而dispatch_async不要求立馬在當前線程同步執行任務,也就是不會堵塞;因此主線程接着執行任務3,最後異步執行任務2。

同步+(全局隊列或者自定義併發隊列)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任務一:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任務二:%@",[NSThread currentThread]);
        }
    });
}
複製代碼

運行結果

咱們在全局隊列中同步執行任務1和任務2,經過打印能夠看出,同步執行是 按順序執行任務,執行完任務1再執行任務2。而且打印當前線程,同步執行是在 主線程中執行任務,沒有開啓新線程。且因爲隊列是併發的,並不會阻塞主線程。

異步+(全局隊列或者自定義併發隊列)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任務一:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任務二:%@",[NSThread currentThread]);
        }
    });
}
複製代碼

運行結果:

能夠看出任務1和任務2交錯執行,並不是同步執行那樣執行完任務1再執行任務2。並且經過線程編號能夠看出,的確開啓了新的線程。說明異步執行具有開啓新線程的能力。

可是經過上面異步+主隊列的打印結果咱們能夠發現,在主隊列時,異步執行也並無開啓新的線程,而仍然是同一個主線程。說明若是在主隊列中異步執行,是不會開啓新線程的

緣由是由於主隊列是串行隊列,必須執行完一個任務再執行另外一個任務,異步執行只是沒有形成堵塞,可是在主隊列仍是是要一步步走的。

同一串行隊列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ 
        NSLog(@"任務2");
        dispatch_sync(queue, ^{ 
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

運行結果

任務1,任務5,任務2,而後程序崩潰

這裏形成死鎖崩潰的緣由和上面同步+串行隊列的緣由同樣。因爲主隊列是串行的,因此代碼必然是從上到下依次執行的。

打印完任務1後,執行dispatch_async爲異步,不會阻塞線程,可是會加入自定義的串行隊列中,因此會執行任務5,接着執行異步代碼塊中邏輯,打印任務2,接着執行dispatch_sync代碼塊,可是因爲其是同步,串行隊列中已經有了dispatch_async任務未執行完畢,此時dispatch_sync阻塞當前線程等待任務3執行,形成了相互等待,因此就死鎖崩潰了

不一樣串行隊列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_sync(queue2, ^{
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

運行結果

任務1,任務5,任務2,任務3,任務4

經過運行咱們能夠發現,當嵌套執行時,同步異步在不一樣的串行隊列執行,並不會形成死鎖崩潰。而是按照串行的順序,執行代碼。這事由於dispatch_sync阻塞的並非當前的線程,而是其餘的線程,因此不會形成死鎖等待。

同一併發隊列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_sync(queue, ^{
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

運行結果:

任務1,任務5,任務2,任務3,任務4

主線程執行任務1以後,須要異步dispatch_async執行任務2;因此先執行主線程的任務5,而後執行任務2;接着須要在併發隊列中同步dispatch_sync執行任務3,因此會形成阻塞,等待其執行完成,而後執行併發隊列中的任務4。

不一樣併發隊列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_sync(queue2, ^{ 
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

運行結果:

任務1,任務5,任務2,任務3,任務4

這個代碼的分析和不一樣串行隊列嵌套的相似,建立了新的隊列,因此是不會形成當前隊列的阻塞的。

總結

  • 同步執行沒有開闢線程能力,且代碼順序執行,會產生堵塞
  • 同步執行在同一串行隊列時,會形成死鎖等待
  • 異步執行具備開闢線程的能力,在併發隊列執行時,執行順序不肯定,在串行隊列執行時,按照順序執行,且主隊列時不會開闢新線程

參考資料

蘋果多線程開源代碼

iOS底層原理探索—多線程的本質

相關文章
相關標籤/搜索