Grand Central Dispatch回顧

原創文章轉載請注明出處,謝謝


重溫了一遍關于GCD方面的一些知識,于是重新整理了一下。

關于Dispatch Queue

Dispatch Queue可以分為兩種隊列,一種是等待現在執行中處理的Serial Dispatch Queue,即串行執行;另一種是不等待現在執行中處理的Concurrent Dispatch Queue,即并行執行;

Concurrent Dispatch Queue并行執行的處理數是由CPU核數,CPU負荷以及Dispatch Queue中的處理所決定。

關于dispatch-queue-create

dispatch-queue-create用于創建Dispatch Queue,可以創建Serial Dispatch Queue和Concurrent Dispatch Queue兩種。

在《Objective-C高級編程iOS與OSX多線程和內存管理》一書中說到dispatch-queue-create不受ARC控制,需要我們自己手動disaptch-retain和dispatch-release,這個其實是不對的。在官方的文檔中已經說明在OSX10.8和iOS10.6以后,ARC已經支持自動管理Dispatch Queue的創建了,不要我們手動release和retain了;但是如果你需要在開啟ARC的情況下同時手動retian/release,那么就需要在compiler flags設置-DOS-OBJECT-USE-OBJC = 0。

關于Main Disaptch Queue和Global Disaptch Queue

Main Disaptch Queue和Global Disaptch Queue是兩個系統的標準Dispatch Queue。

Main Disaptch Queue是在主線程中執行的Dispatch Queue,因為主線程只有一個,所以Main Dispatch Queue就是Serial Dispatch Queue。

Global Disaptch Queue是所有線程都可以使用的Concurrent Dispatch Queue,Global Disaptch Queue有四個執行優先級:

  • High Priority(最高優先級)
  • Default Priority(默認優先級)
  • Low Priority(低優先級)
  • Background Priority(后臺優先級)

但是通過XNU內核用于Global Disaptch Queue的線程并不能保持實時性,因此執行優先級只是大致的判斷。Global Disaptch Queue的默認執行優先級是Default Priority。

/*
 * Main Dispatch Queue
 */
 dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
 
 /*
  * Global Dispatch Queue(High Priority)
  */
 dispatch_queue_t globalDispatchQueueHigh = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 
 /*
  * Global Dispatch Queue(Default Priority)
  */
 dispatch_queue_t globalDispatchQueueDefault = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
  /*
  * Global Dispatch Queue(Low Priority)
  */
 dispatch_queue_t globalDispatchQueueLow = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
 
  /*
  * Global Dispatch Queue(Background Priority)
  */
 dispatch_queue_t globalDispatchQueueBackground = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
 

關于dispatch-set-target-queue

dispatch-set-target-queue一共有兩個作用。

作用一:修改Dispatch Queue的執行優先級;通過dispatch-queue-create函數生成的Dispatch Queue默認優先級都是Default Priority。

/*
 *修改serialQueue優先級至Background Priority
 */
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);  
dispatch_set_target_queue(serialQueue, globalQueue); 

注意不要修改Main Disaptch Queue和Global Disaptch Queue的優先級,因為這種情況是不可預知的。

作用二:修改用戶隊列的目標隊列,使多個Serial Dispatch Queue在目標Queue只能同時執行一個處理,防止并行執行。

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);  
      
dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_SERIAL);  
dispatch_queue_t queue3 = dispatch_queue_create("com.example.gcd.queue3", DISPATCH_QUEUE_SERIAL);  
      
dispatch_set_target_queue(queue1, serialQueue);  
dispatch_set_target_queue(queue2, serialQueue);  
dispatch_set_target_queue(queue3, serialQueue);  
      
      
dispatch_async(queue1, ^{  
    NSLog(@"queue1-start");
    sleep(1.0f);
    NSLog(@"queue1-end");
});  
  
dispatch_async(queue2, ^{  
    NSLog(@"queue2-start");
    sleep(1.0f);
    NSLog(@"queue2-end");
});  
dispatch_async(queue3, ^{  
    NSLog(@"queue3-start");
    sleep(1.0f);
    NSLog(@"queue3-end");
});  

//out put  多個Serial Queue并發執行,每次只能執行一個serial Queue的內容
queue1-start
queue1-end
queue2-start
queue2-end
queue3-start
queue3-end

關于dispatch-after和dispatch-once

dispatch-after函數并不是在指定時間后執行處理,而是在指定時間追加處理時間到Dispatch queue后再進行執行。

關于dispatch-time-t的類型可以由dispatch-time和dispatch-walltime兩個函數來生成。

dispatch-time函數能夠獲取從第一個參數dispatch-time-t類型值中指定時間開始,到第二個參數指定的毫微秒單位時間后的時間。此時間是指相對時間。

// 延時一秒以后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

dispatch-walltime函數能夠獲取從第一個參數struct timespec結構體時間開始,此時間是指絕對時間。

struct timespec類型的時間可以通過NSDate類對象轉換而成。

NSDate *date = [NSDate date];
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
    
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone milestone = dispatch_walltime(&time, 0);

dispatch-once函數的目的是保證在應用程序中執行中只執行指定處理,經常出現在單例的初始化里面,通過disptach-once函數,即使在多線程環境下執行也是安全的。

static dispatch_once_t once;

dispatch_once(&once, ^{
    // init class
});

關于Dispatch Group的作用

Dispatch Group的作用是當隊列中的所有任務都執行完畢后在去做一些操作,主要針對Concurrent Dispatch Queue中多個處理結束后追加的操作。

Dispatch Group分為兩種方式可以實現上面需求。

第一種是使用dispatch-group-async函數,將隊列與任務組進行關聯并自動執行隊列中的任務。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

    // output
    blk3
    blk2
    blk1
    done

dispatch-group-async函數會將隊列與相應的任務組進行關聯同時自動執行,當與任務組關聯的隊列中的任務都執行完畢后,會通過dispatch-group-notify函數發出通知告訴用戶任務組中的所有任務都執行完畢了,有點類似于dispatch-barrier-async,另外dispatch-group-notify方式并不會阻塞線程。

但是如果我們使用dispatch-group-wait函數,那么就會阻塞當前線程,等待全部處理執行結束。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"done");
    
    //output
    blk2
    blk3
    blk1
    done

dispatch-group-wait函數的返回值不為0就意味著雖然經過了指定的時間,但是屬于Dispatch Group的某一個處理還在執行中,如果返回值為0為全部執行結束,當等待時間為DISPATCH-TIME-FOREVER,由dispatch-group-wait函數返回時,由于屬于Dispatch Group的處理必定全部執行結束,因此返回值一直為0。當指定為DISPATCH-TIME-NOW則不用任何等待即可判定屬于Dispatch Group的處理是否執行結束。

第二種是使用手動的將隊列與組進行關聯然后使用異步將隊列進行執行,也就是dispatch-group-enter與dispatch-group-leave方法的使用。dispatch-group-enter函數進入到任務組中,然后異步執行隊列中的任務,最后使用dispatch-group-leave函數離開任務組即可。

    dispatch_queue_t queue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"blk3");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });
    
    // output
    blk2
     blk3
     blk1
     done

dispatch-group-enter和dispatch-group-leave必須同時匹配出現才可以,不然就會出現無法預知的情況。

關于dispatch-barrier-async

dispatch-barrier-async函數的目的基本就是為了讀寫鎖的問題。

對于Concurrent Dispatch Queue可能會所產生的數據庫同時讀寫的問題,使用dispatch-barrier-async就可以很好的避免這個問題。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    __block NSInteger index = 0;
    dispatch_async(queue, ^{NSLog(@"blk0_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk1_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk2_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk3_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk4_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk5_for_reading index %ld", index);});
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"----------------------------------");
        index++;
    });
    
    dispatch_async(queue, ^{NSLog(@"blk6_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk7_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk8_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk9_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk10_for_reading index %ld", index);});
    dispatch_async(queue, ^{NSLog(@"blk11_for_reading index %ld", index);});
    
//output
blk0_for_reading index 0
blk1_for_reading index 0
blk2_for_reading index 0
blk3_for_reading index 0
blk5_for_reading index 0
blk4_for_reading index 0
----------------------------------
blk6_for_reading index 1
blk7_for_reading index 1
blk8_for_reading index 1
blk9_for_reading index 1
blk10_for_reading index 1
blk11_for_reading index 1

dispatch-barrier-sync 函數會等待追加到Concurrent Dispatch Queue上的并行執行的處理全部結束以后,再將指定的處理追加到該Concurrent Dispatch Queue中。然后在由Concurrent Dispatch Queue函數追加的處理執行完畢后。Concurrent Dispatch Queue才恢復為一般的動作,追加到該Concurrent Dispatch Queue的處理又開始并行執行。

關于dispatch-suspend(掛起)/dispatch-resume(恢復)

dispatch-suspend/dispatch-resume一般用于當追加大量處理到Dispatch Queue時,在追加處理的過程中有時希望不執行已追加的處理。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_suspend(queue);
    dispatch_async(queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk1");
    });
    sleep(2);
    dispatch_resume(queue);
    // 兩秒后恢復queue,然后才會執行queue里面的操作

關于dispatch-sync

dispatch-sync相對于dispatch-async的區別就在于它是就是同步的線程操作,只有指定的block完成以后dispatch-sync才會返回。

但是dispatch-sync會帶來一些死鎖的情況。

將主線程的Main Disaptch Queue,在主線程中執行dispatch_sync就會造成死鎖。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"lock");
    });

因為主線程正在執行上述代碼塊,所以此時的block無法執行到Main Disaptch Queue,由于Main Disaptch Queue是Serial Dispatch Queue;但是由于block無法執行,所以dispatch-sync就會一直等待block的執行,主線程此時死鎖。

下面的例子也是同樣的道理。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

所以由此可知,我把Main Disaptch Queue替換成任何Serial Dispatch Queue都會造成死鎖的問題。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.queue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

關于Dispatch Semaphore的問題

Dispatch Semaphore主要用于信號量的控制并發,當處理一系列線程的時候,當數量達到一定量時,我們需要控制線程的并發量。

在GCD中有三個函數是semaphore的操作,分別是:

  • dispatch-semaphore-create   創建一個初始指定數量的信號量
  • dispatch-semaphore-signal   發送一個信號,使信號量+1,計數為0時等待,計數為1或大于1時,減去1而不等待。
  • dispatch-semaphore-wait    等待信號,使信號量-1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 50; index++) {
        /**
         *等待處理,直到信號量>0,信號量減1,當信號量為0時不需要在減1
         */
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(global, ^{
            NSLog(@"thread %ld", index);
            /**
             *數據處理完畢,信號量加1
             */
            dispatch_semaphore_signal(semaphore);
        });
    }

總結

GCD算是OC中一項很基礎的知識了,靈活使用GCD會很大程度上提高我們的代碼質量。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
    SkyMing一C閱讀 1,672評論 0 22
  • 我們知道在iOS開發中,一共有四種多線程技術:pthread,NSThread,GCD,NSOperation: ...
    請叫我周小帥閱讀 1,507評論 0 1
  • 背景 擔心了兩周的我終于輪到去醫院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌),之前在網上查的癥狀都很相似。...
    Dely閱讀 9,270評論 21 42
  • 同步/異步 同步:多個任務情況下,一個任務A執行結束,才可以執行另一個任務B。只存在一個線程也就是主線程。 異步:...
    XLsn0w閱讀 309評論 0 0
  • 一、GCD的API 1. Dispatch queue 在執行處理時存在兩種Dispatch queue: 等待現...
    doudo閱讀 508評論 0 0