iOS開發:深刻理解GCD 第一篇

最近把其餘書籍都放下了,主要是在研究GCD。若是是爲了工做,以我之前所學的GCD、NSOperation等知識已經足夠用了,但學習並不只僅知識知足於用它,要知其然、而且知其因此然,這樣才能夠不斷的提升自身技術水平。ios

本文主要參考http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 和 《iOS與OS X 多線程和內存管理》,以及其餘一些雜七雜八的書籍或者博客。程序員

GCD已經面世好久了,基於GCD面向對象的多線程技術NSOperation也出現好久了,但並非全部人都明瞭GCD主要內容,併發一直很棘手(雖然AFNetworking框架在必定程度上避免了不少時候咱們在程序內赤裸裸的寫多線程代碼,但我想任何一個有想法的程序員都會深刻理解併發編程,並將之掌握的),它們就像一組尖銳的棱角戳進 Objective-C 的平滑世界。編程

在這裏,我將會四個篇幅來梳理本身所知、所理解的GCD。安全

第1、二篇主要是解釋GCD是什麼、能作什麼,會提供我所編寫好的一些代碼片斷,必要時,會有demo。多線程

第三篇和第三篇主要是學習一些高級GCD提供的高級函數,若是時間充足,我會盡可能以文字+代碼+demo展現。併發

 

若是你對GCD和Block(代碼塊)徹底陌生,請先看這篇文章:http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial        《iOS上GCD和多線程的入門教程》框架

 

進程:也就是一個正在運行的應用程序。異步

線程:進程中的某一條完整的執行路徑。一個進程能夠有多個線程,至少有一個線程,即主線程。在iOS開發中,全部涉及UI界面的,必須在主線程中更新。async

 

什麼是GCD?函數

蘋果官方給出的解釋:GCD是異步執行任務的技術之一。通常將應用程序中記述的線程管理代碼在系統集中實現,開發者只須要定義想執行的任務並追加到

適當的Dispatch Queue中,GCD就能夠生成必要的線程並計劃執行任務。

它具備如下優勢:

    1. GCD能夠將花費時間極其長的任務放到後臺線程,能夠改善應用的響應性能
    2. GCD 提供一個易於使用的併發模型而不只僅只是鎖和線程,以幫助咱們避開併發陷阱
    3. GCD 具備在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力(後面會提供一個單例的medo)
    4. 等等

以下面的代碼片斷:

dispatch_queue_t queue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        /**
         
         放一些極其耗時間的任務在此執行
         */
        
        dispatch_async(dispatch_get_main_queue(), ^{
            
            /**
             耗時任務完成,拿到資源,更新UI
             更新UI只能夠在主線程中更新
             */
            
        });
        
    });

 我建立了一個並行隊列queue,並異步執行耗時操做,當耗時操做執行完畢,我拿到其中的資源回到主線程來更新相應的UI,在這個Block代碼塊以外,主線程並不會被耗時任務所堵塞,能夠流暢的處理其餘事情。

 

GCD的一些術語

要理解 GCD ,要先熟悉與線程和併發相關的幾個概念。

串行(Serial)與  併發(Concurrent)

任務串行,意味着在同一時間,有且只有一個任務被執行,即一個任務執行完畢以後再執行下一個任務。

任務併發,意味着在同一時間,有多個任務被執行。

同步(Synchronous)與  異步 (Asynchronous)

同步,意味着在當前線程中執行任務,不具有開啓新的線程的能力。

異步,在新的線程中執行任務,具有開啓新的線程的能力。

在 GCD 中,這些術語描述當一個函數相對於另外一個任務完成,此任務是該函數要求 GCD 執行的。一個同步函數只在完成了它預約的任務後才返回。

一個異步函數,恰好相反,會當即返回,預約的任務會完成但不會等它完成。所以,一個異步函數不會阻塞當前線程去執行下一個函數。

臨界區(Critical Section)

就是一段代碼不能被併發執行,也就是,兩個線程不能同時執行這段代碼。這很常見,由於代碼去操做一個共享資源,例如一個變量若能被併發進程訪問,那麼它極可能會變質(它的值再也不可信)。

死鎖(Deadlock)

中止等待事情的線程會致使多個線程相互維持等待,即死鎖。

兩個(有時更多)東西——在大多數狀況下,是線程——所謂的死鎖是指它們都卡住了,並等待對方完成或執行其它操做。第一個不能完成是由於它在等待第二個的完成。但第二個也不能完成,由於它在等待第一個的完成。

代碼片斷:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"111111");
    });
    
    NSLog(@"222222");
    
}

執行上面的代碼,你會發現沒有任何打印,這個時候就是發生了死鎖,咱們禁止在主隊列(iOS開發中,主隊列是串行隊列)中,在同步使用主隊列執行任務,同理,禁止在同一個同步串行隊列中,再使用該串行隊列同步的執行任務,由於這樣會形成死鎖。

代碼片斷:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"111111");
        
        dispatch_sync(queue, ^{
            NSLog(@"22222");
        });
        
        NSLog(@"3333333");
        
    });
    NSLog(@"44444444");
}

 會發現,只是打印了一次,而後就形成了死鎖。

 

線程安全(Thread Safe)

線程安全的代碼能在多線程或併發任務中被安全的調用,而不會致使任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你能夠在同一時間在多個線程中使用它而不會有問題。另外一方面,NSMutableDictionary 就不是線程安全的,應該保證一次只能有一個線程訪問它。

上下文切換(Context Switch)

一個上下文切換指當你在單個進程裏切換執行不一樣的線程時存儲與恢復執行狀態的過程。這個過程在編寫多任務應用時很廣泛,但會帶來一些額外的開銷。

併發與並行

並行要求併發,但併發不能保證並行,就計算機操做系統來講,開啓線程是很耗性能的,也就是說,事實上,在某次並行處理任務中,開啓的線程是有上限的,若是上限爲2,即每次開啓的新線程爲2,那麼是有可能出現併發卻不併行的狀況。

併發代碼的不一樣部分能夠「同步」執行。然而,該怎樣發生或是否發生都取決於系統。多核設備經過並行來同時執行多個線程;然而,爲了使單核設備也能實現這一點,它們必須先運行一個線程,執行一個上下文切換,而後運行另外一個線程或進程。這一般發生地足夠快以至給咱們併發執行地錯覺,以下圖所示: 

 

隊列(Queue)

 蘋果官方對GCD的說明:開發者要作的只是定義想執行的任務並追加到適當的Dispatch Queue中。

 這句話的源碼以下:

dispatch_async(queue, ^{
        
        /**
         *  想要執行的任務
         */
        
    });

 該源碼使用Block語法「定義想執行的任務」,經過dispatch_async函數「追加」賦值在變量queue的"Dispatch Queue中"。僅僅是這樣,就可使得指定的Block在另外一線程中執行。

GCD 提供有 dispatch queue 來處理代碼塊,這些隊列管理你提供給 GCD 的任務並用 FIFO (先進先出)順序執行這些任務。這就保證了第一個被添加到隊列裏的任務會是隊列中第一個開始的任務,而第二個被添加的任務將第二個開始,如此直到隊列的終點。

Dispatch Queue是什麼呢?是執行處理的等待隊列,程序員經過dispatch_async等API,在Block語法中記述想要執行的處理,並將其追加到Dispatch Queue中。Dispatch Queue按照追加的順序進行處理。

 

 

全部的調度隊列(dispatch queue)自身都是線程安全的,你能從多個線程並行的訪問它們。 GCD 的優勢是顯而易見的,即當你瞭解了調度隊列如何爲你本身代碼的不一樣部分提供線程安全。關於這一點的關鍵是選擇正確類型的調度隊列和正確的調度函數來提交你的工做。

另外,在執行處理時,存在本文前面提到的兩種Dispatch Queue,一種是等待如今執行中處理的Serial Dispatch Queue,另外一種是Concurrent Dispatch Queue。

 

串行隊列(Serial Dispatch Queue ) 

這些任務的執行時機受到 GCD 的控制;惟一能確保的事情是 GCD 一次只執行一個任務,而且按照咱們添加到隊列的順序來執行。

以下代碼,當調用serialPrintNumber方法時:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) dispatch_queue_t serialQueue;

@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.serialQueue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_SERIAL);
    self.concurrentQueue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 100; i++) {
        [self serialPrintNumber:i];
    }
    
}
/**
 *  異步串行隊列
 *
 */
- (void)serialPrintNumber:(int)number
{
    dispatch_async(self.serialQueue, ^{
        
        NSLog(@"%d   %@",number, [NSThread currentThread]);
        
    });
}

/**
 *  異步並行隊列
 *
 */
- (void)concurrentPrintNumber:(int)number
{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"%d   %@",number, [NSThread currentThread]);
    });
}
@end

 能夠看到這樣的打印:

 開闢了一個新的子線程,任務是按順序執行的。先進先出順序執行的。由於要等待前一個任務處理結束,即同一時間,只能處理一個任務,才能夠開始處理下一任務。

 

併發隊列(Concurrent Dispatch Queue)

在併發隊列中的任務能獲得的保證是它們會按照被添加的順序開始執行,但這就是所有的保證了。任務可能以任意順序完成,你不會知道什麼時候開始運行下一個任務,或者任意時刻有多少 Block 在運行。再說一遍,這徹底取決於 GCD

代碼片斷:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) dispatch_queue_t serialQueue;

@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.serialQueue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_SERIAL);
    self.concurrentQueue = dispatch_queue_create("cn.chutong.www", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 100; i++) {
        [self concurrentPrintNumber:i];
    }
    
}
/**
 *  異步串行隊列
 *
 */
- (void)serialPrintNumber:(int)number
{
    dispatch_async(self.serialQueue, ^{
        
        NSLog(@"%d   %@",number, [NSThread currentThread]);
        
    });
}

/**
 *  異步並行隊列
 *
 */
- (void)concurrentPrintNumber:(int)number
{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"%d   %@",number, [NSThread currentThread]);
    });
}
@end

 打印:

 

能夠看到,再也不是順序執行任務,開了多個線程。至此,咱們能夠清楚的知道,所謂「並行執行」,就是使用多個線程同時處理多個任務,由於完成任務所須要消耗的時間不一樣,因此完成任務的最終時間不一樣。

Serial Dispatch Queue 與 Concurrent Dispatch Queue 和線程之間的關係,以下圖:

相關文章
相關標籤/搜索