GCD系列 之(一):基本概念和Dispatch Queue

參考學習https://www.dreamingwish.com/article/grand-central-dispatch-basic-1.html系列文章,貌似也是翻譯自他處的。以爲很是完整,就本身搬運過來,備忘和分享。防止之後遇到問題, 想起來,可是又找不到的痛苦html

 

什麼是GCD?git

Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進行併發程序編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都容許程序將任務切分爲多個單一任務而後提交至工做隊列來併發地或者串行地執行。GCD比之NSOpertionQueue更底層更高效,而且它不是Cocoa框架的一部分。github

除了代碼的平行執行能力,GCD還提供高度集成的事件控制系統。能夠設置句柄來響應文件描述符、mach ports(Mach port 用於 OS X上的進程間通信)、進程、計時器、信號、用戶生成事件。這些句柄經過GCD來併發執行。編程

GCD的API很大程度上基於block,固然,GCD也能夠脫離block來使用,好比使用傳統c機制提供函數指針和上下文指針。實踐證實,當配合block使用時,GCD很是簡單易用且能發揮其最大能力。安全

你能夠在Mac上敲命令「man dispatch」來獲取GCD的文檔。多線程

爲什麼使用?併發

GCD提供不少超越傳統多線程編程的優點:框架

  1. 易用: GCD比之thread跟簡單易用。因爲GCD基於work unit而非像thread那樣基於運算,因此GCD能夠控制諸如等待任務結束監視文件描述符週期執行代碼以及工做掛起等任務。基於block的血統致使它能極爲簡單得在不一樣代碼做用域之間傳遞上下文。
  2. 效率: GCD被實現得如此輕量和優雅,使得它在不少地方比之專門建立消耗資源的線程更實用且快速。這關係到易用性:致使GCD易用的緣由有一部分在於你能夠不用擔憂太多的效率問題而僅僅使用它就好了。
  3. 性能: GCD自動根據系統負載來增減線程數量,這就減小了上下文切換以及增長了計算效率。

Dispatch Objects異步

儘管GCD是純c語言的,但它被組建成面向對象的風格。GCD對象被稱爲dispatch object。Dispatch object像Cocoa對象同樣是引用計數的。使用dispatch_release和dispatch_retain函數來操做dispatch object的引用計數來進行內存管理。但主意不像Cocoa對象,dispatch object並不參與垃圾回收系統,因此即便開啓了GC,你也必須手動管理GCD對象的內存。async

Dispatch queues 和 dispatch sources(後面會介紹到)能夠被掛起和恢復,能夠有一個相關聯的任意上下文指針,能夠有一個相關聯的任務完成觸發函數。能夠查閱「man dispatch_object」來獲取這些功能的更多信息。

Dispatch Queues

GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它能夠接受任務,並將任務以先到先執行的順序來執行。dispatch queue能夠是併發的或串行的。併發任務會像NSOperationQueue那樣基於系統負載來合適地併發進行,串行隊列同一時間只執行單一任務。

GCD中有三種隊列類型:

  1. The main queue: 與主線程功能相同。實際上,提交至main queue的任務會在主線程中執行。main queue能夠調用dispatch_get_main_queue()來得到。由於main queue是與主線程相關的,因此這是一個串行隊列。
  2. Global queues: 全局隊列是併發隊列,並由整個進程共享。進程中存在三個全局隊列:高、中(默認)、低三個優先級隊列。能夠調用dispatch_get_global_queue函數傳入優先級來訪問隊列。
  3. 用戶隊列: 用戶隊列 (GCD並不這樣稱呼這種隊列, 可是沒有一個特定的名字來形容這種隊列,因此咱們稱其爲用戶隊列) 是用函數 dispatch_queue_create 建立的隊列. 這些隊列是串行的。正由於如此,它們能夠用來完成同步機制, 有點像傳統線程中的mutex。

建立隊列

要使用用戶隊列,咱們首先得建立一個。調用函數dispatch_queue_create就好了。函數的第一個參數是一個標籤,這純是爲了debug。Apple建議咱們使用倒置域名來命名隊列,好比「com.dreamingwish.subsystem.task」。這些名字會在崩潰日誌中被顯示出來,也能夠被調試器調用,這在調試中會頗有用。第二個參數目前還不支持,傳入NULL就好了。

提交 Job

向一個隊列提交Job很簡單:調用dispatch_async函數,傳入一個隊列和一個block。隊列會在輪到這個block執行時執行這個block的代碼。下面的例子是一個在後臺執行一個巨長的任務:

    //1.提交 Job
                                            //優先級爲Default
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //global_queue會在輪到這個block執行時執行這裏的代碼
        [self goDoSomethingLongAndInvolved];
        NSLog(@"Done doing something long and involved");
    });

 

dispatch_async 函數會當即返回, block會在後臺異步執行。 因此後面的代碼跟block裏面的代碼的執行順序不肯定

固然,一般,任務完成時簡單地NSLog個消息不是個事兒。在典型的Cocoa程序中,你頗有可能但願在任務完成時更新界面,這就意味着須要在主線程中執行一些代碼。你能夠簡單地完成這個任務——使用嵌套的dispatch,在外層中執行後臺任務,在內層中將任務dispatch到main queue:

    //2.使用嵌套的dispatch,異步拋任務給後臺執行,後臺異步執行完任務以後,dispatch回到main queue,執行更新UI
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //異步執行操做序列
        [self goDoSomethingLongAndInvolved];
        
        //異步執行完任務以後須要回到main_queue作UI頁面更新等等
        dispatch_async(dispatch_get_main_queue(), ^{
            //
            [self.sumLabel setText:[NSString stringWithFormat:@"%ld",self.sum]];
        });
    });

dispatch_sync,同步執行,等待block中的代碼執行完成並返回,而後執行接下來的任務。用 __block類型修飾符,能夠用來從執行中的block獲取一個值。例如,你可能有一段代碼異步拋到後臺去執行,而它須要從界面控制層獲取一個值。那麼你可使用dispatch_sync在異步操做中暫停一下,而後作完這個同步操做,取到UI上的值,而後再進行異步操做的接下來代碼序列:

#pragma mark - 適合異步拋給後臺作的事情
-(void)goDoSomethingLongAndInvolved{
    NSInteger  sum = 0;
    for (int i = 0; i<50000; i++) {
        sum = sum + i;
    }
    
    //3.dispatch_sync同步操做  將同步操做放在異步拋給後臺執行的代碼中,就能夠,先等這個同步操做執行完以後,再接着進行下面的異步操做序列
//這個有阻塞了後臺的線程
__block NSString *stringValue; dispatch_sync(dispatch_get_main_queue(), ^{ //獲取主線程裏的UI中的Label的值,累加上Label的值 stringValue = self.sumLabel.text; NSLog(@"實現累加上次結果的功能"); }); sum = sum + stringValue.integerValue; self.sum = sum; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }

上面的效果也能夠這樣實現:

 //用嵌套的block來停止後臺線程,而後從主線程中獲取值,而後再將後期處理提交至後臺線程:
 dispatch_queue_t bgQueue = myQueue;
 dispatch_async(dispatch_get_main_queue(), ^{
 NSString *stringValue = sumLabel.text;
 dispatch_async(bgQueue, ^{
 // use stringValue in the background now
 });
 });
 //取決於你的需求,myQueue能夠是用戶隊列也可使全局隊列。

再也不使用鎖(Lock)

用戶隊列能夠用於替代鎖來完成同步機制。在傳統多線程編程中,你可能有一個對象要被多個線程使用,你須要一個鎖來保護這個對象:

    NSLock *lock;
訪問代碼會像這樣:
- (id)something
{
    id localSomething;
    [lock lock];
    localSomething = [[something retain] autorelease];
    [lock unlock];
    return localSomething;
}
 
- (void)setSomething:(id)newSomething
{
    [lock lock];
    if(newSomething != something)
    {
        [something release];
        something = [newSomething retain];
        [self updateSomethingCaches];
    }
    [lock unlock];
}

使用GCD,可使用queue來替代鎖:

    dispatch_queue_t queue;

要用於同步機制,queue必須是一個用戶隊列(從OS X v10.7和iOS 4.3開始,還必須指定爲DISPATCH_QUEUE_SERIAL),而非全局隊列,因此使用dispatch_queue_create初始化一個。而後能夠用dispatch_async 或者 dispatch_sync將共享數據的訪問代碼封裝起來:

- (id)something
{
    __block id localSomething;
    dispatch_sync(queue, ^{
        localSomething = [something retain];
    });
    return [localSomething autorelease];
}
 
- (void)setSomething:(id)newSomething
{
    dispatch_async(queue, ^{
        if(newSomething != something)
        {
            [something release];
            something = [newSomething retain];
            [self updateSomethingCaches];
        }
    });
}

使用GCD途徑有幾個好處:

  1. 平行計算: 注意在第二個版本的代碼中, -setSomething:是怎麼使用dispatch_async的。調用 -setSomething:會當即返回,而後這一大堆工做會在後臺執行。若是updateSomethingCaches是一個很費時費力的任務,且調用者將要進行一項處理器高負荷任務,那麼這樣作會很棒。
  2. 安全: 使用GCD,咱們就不可能意外寫出具備不成對Lock的代碼。在常規Lock代碼中,咱們極可能在解鎖以前讓代碼返回了。使用GCD,隊列一般持續運行,你必將歸還控制權。
  3. 控制: 使用GCD咱們能夠掛起和恢復dispatch queue,而這是基於鎖的方法所不能實現的。咱們還能夠將一個用戶隊列指向另外一個dspatch queue,使得這個用戶隊列繼承那個dispatch queue的屬性。使用這種方法,隊列的優先級能夠被調整——經過將該隊列指向一個不一樣的全局隊列,如有必要的話,這個隊列甚至能夠被用來在主線程上執行代碼。
  4. 集成: GCD的事件系統與dispatch queue相集成。對象須要使用的任何事件或者計時器均可以從該對象的隊列中指向,使得這些句柄能夠自動在該隊列上執行,從而使得句柄能夠與對象自動同步。

練習demo地址:https://github.com/Jordan150513/OCDemos.git中的GCDDemos項目

相關文章
相關標籤/搜索