GCD理解-Group、Queue、Concurrent、Serial、async、sync

GCD

Dispatch Queue

執行處理的等待隊列。應用程序編程人員通過dispatch_async函數等API,在Block語法中記述想執行的處理並將其追加到Dispatch Queue中,Dispatch Queue按照追加的順序(先進先出FIFO)執行處理。

串行隊列(Serial Dispatch Queue)

因爲要等待現在執行中的處理結束,所以首先執行blk0,blk0執行結束後,接着執行blk1,blk1結束後再開始執行blk2,如此重複。同時執行的處理數只能有1個。

​ 只在爲了避免多線程編程問題之一——多個線程更新相同資源導致數據競爭時使用Serial Dispatch Queue

​ Serial Dispatch Queue的生成個數應當僅限所必需的數量,例如更新數據庫時一個表生成一個Serial Dispatch Queue,更新文件時一個文件或是可以分割的1個文件塊生成1個Serial Dispatch Queue。絕不能激動之下大量生成Serial Dispatch Queue

併發隊列(Concurrent Dispatch Queue)

因爲不用等待現在執行中的處理結束,所以首先執行blk0,不管blk0的執行是否結束,都開始執行後面的blk1,不管blk1的執行是否結束,都開始執行後面的blk2,如此重複循環

​ 這樣雖然不用等待處理結束,可以並行執行多個處理,但並行執行的處理數量取決於當前系統的狀態。即iOS和OS X基於Dispatch Queue中的處理數,CPU核數以及CPU負荷等當前系統的狀態來決定併發隊列中並行執行的處理數

​ iOS和OS X的核心——XNU內核決定應當使用的線程數,並只生成所需的線程執行處理。另外,當處理結束,應當執行的處理數減少時,XNU內核會結束不再需要的線程。

​ 假設準備4個Concurrent Dispatch Queue用線程,首先blk0再線程0中開始執行,接着blk1在線程1中、blk2在線程2中、blk3在線程3中開始執行。線程0中blk0執行結束後開始執行blk4,由於線程1中blk1的執行沒有結束,因此線程2中blk2執行結束後開始執行blk5,就這樣循環往復

dispatch_queue_create

dispatch_queue_create可生成Dispatch Queue,雖然Serial Dispatch Queue和Concurrent Dispatch Queue受到系統資源的限制,但用dispatch_queue_create函數可生成任意多個Dispatch Queue

​ 當生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將並行執行。雖然在1個Serial Dispatch Queue中同時只能執行一個追加處理,但如果將處理分別追加到4個Serial Dispatch Queue中,各個Serial Dispatch Queue執行1個,即爲同時執行4個處理。

​ 對於Concurrent Dispatch Queue來說,不管生成多少,由於XNU內核只使用有效管理的線程,因此不會發生Serial Dispatch Queue的那些問題

​ 在iOS6.0以下通過dispatch_queue_create生成的Dispatch Queue在使用結束後通過dispatch_release函數釋放,iOS6.0以及以上ARC會自動管理

系統提供的Dispatch Queue

​ Main Dispatch Queue是在主線程執行的Dispatch Queue,因爲主線程只有一個,所以Main Dispatch Queue自然就是Serial Dispatch Queue,追加到Main Dispatch Queue的處理在主線程的RunLoop中執行。

​ Global Dispatch Queue有四個執行優先級,4個全局隊列,分別是高優先級(High Priority)、默認優先級(Default Priority)、低優先級(Low Priority)和後臺優先級(Background Priority)。通過XNU內核管理的用於Global Dispatch Queue的線程,將各自使用的Global Dispatch Queue的執行優先級作爲線程的執行優先級使用。

爲創建的Dispatch Queue創建優先級

​ 自己創建的Dispatch Queue優先級都使用與默認優先級Global Dispatch Queue相同執行優先級的線程。而變更自己創建的Dispatch Queue執行優先級要使用dispatch_set_target_queue函數

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",NULL);
dispatch_queue_ t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

iOS 8之後建議用dispatch_queue_attr_t設置優先級

//iOS 8以上 
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
dispatch_queue_t queues = dispatch_queue_create("com.yh.render", attr);

延遲執行(dispatch_after)

​ 想在指定時間後執行處理的情況,可使用dispatch_after函數來實現

​ 需要注意的是, dispatch_after函數並不是在指定時間後執行處理,而只是在指定時間追加處理到Dispatch Queue。此源代碼與在3秒後用dispatch_async函數追加Block到Main Dispatch Queue的相同。

​ 因爲Main Dispatch Queue在主線程的RunLoop中執行,所以在比如每隔1/60秒執行的RunLoop中,Block最快在3秒後執行,最慢在3秒+1/60秒後執行,並且在Main Dispatch Queue有大量處理追加或主線程的處理本身有延遲時,這個時間會更長

// 從第一個參數中指定的時間開始,到第二個參數指定的毫微秒單位時間後的時間
    // ull 是C語言的數值字面量,是顯式表明類型時使用的字符串("unsigned long long")
//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
   
   // 第一個參數是指定時間用的dispatch_time_t類型值,該值使用dispatch_time函數或dispatch_walltime函數作成
    // dispatch_walltime用於計算絕對時間
    NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
    double second;
    double subsecond = modf(interval, &second);
    
    struct timespec time;
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    dispatch_time_t miletone = dispatch_walltime(&time, 0);
   
    dispatch_after(miletone, dispatch_get_main_queue(), ^{
       
        NSLog(@"waited at least three seconds");
    });

Dispatch Group

​ 1. 在追加到Dispatch Queue中的多個處理全部結束後想執行結束處理。無論向什麼樣的Dispatch Queue中追加處理,使用Dispatch Group都可監視這些處理執行的結束。一旦檢測到所有處理執行結束,就可將結束的處理追加到Dispatch Queue中

在追加到Dispatch Group中的處理全部執行結束時,該源代碼中使用的dispatch_group_notify函數會將執行的Block追加到Dispatch Queue中,將第一個參數指定爲要監視的Dispatch Group。在追加到該Dispatch Group的全部處理執行結束時,將第三個參數的Block追加到第二個參數的Dispatch Queue中。

​ 2. 也可以使用dispatch_group_wait函數僅等待全部處理執行結束

dispatch_group_wait函數的第二個參數指定爲等待的時間(超時)。它屬於dispatch_time_t類型的值。一旦調用dispatch_group_wait函數,該函數就處於調用的狀態而不返回。即執行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, ^{
        sleep(2);
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        sleep(2);
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        sleep(6);
        NSLog(@"blk2");
    });
    // 不會阻礙主線程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       NSLog(@"done");
    });
    // 會讓dispatch_group_wait的當前線程停止(這裏是寫在主線程上);
//    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//    if (result == 0) {
//       NSLog(@"done");
//    }
dispatch_group_enter、dispatch_group_leave
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第一個走完了");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第二個走完了");
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有任務完成,可以更新UI");
    });

Dispatch Group的本質是一個初始value爲LONG_MAX的semaphore,通過信號量來實現一組任務的管理

dispatch_group_enter的邏輯是將dispatch_group_t轉換成dispatch_semaphore_t後將dsema_value的值減一。

dispatch_group_leave的邏輯是將dispatch_group_t轉換成dispatch_semaphore_t後將dsema_value的值加一。

當調用了dispatch_group_enter而沒有調用dispatch_group_leave時,會造成value值不等於LONG_MAX而不會走到喚醒邏輯,dispatch_group_notify函數的block無法執行或者dispatch_group_wait收不到semaphore_signal信號而卡住線程。

當dispatch_group_leave比dispatch_group_enter多調用了一次時,dispatch_semaphore_t的value會等於LONGMAX+1(2147483647+1),即long的負數最小值LONG_MIN(–2147483648)。因爲此時value小於0,所以會出現"Unbalanced call to dispatch_group_leave()"的崩潰

dispatch_barrier_async

多讀單寫問題:爲了高效率地進行訪問,讀取處理可以並行執行,寫入處理不可以與其他寫入處理以及包含讀取處理並行執行

​ dispatch_barrier_async,該函數同dispatch_queue_create函數生成的Concurrent Dispatch Queue一起使用(全局隊列使用無效)

​ dispatch_barrier_async函數會等待追加到Concurrent Dispatch Queue上的並行執行的處理全部結束之後,再將指定的處理追加到該Concurrent Dispatch Queue中。然後再由dispatch_barrier_async函數追加的處理執行完畢後,Concurrent Dispatch Queue才恢復爲一般的動作,追加到該Concurrent Dispatch Queue的處理又開始並行執行。

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(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"donw");
        });
        NSLog(@"dispatch_group_wait 結束");
    });

dispatch_sync

dispatch_async:不做任何等待,將指定的Block"非同步"地追加到指定的Dispatch Queue中

dispatch_sync: 等待處理執行結束, 將指定的Block"同步"地追加到指定的Dispatch Queue中,如dispatch_group_wait說明:"等待"意味着當前線程停止

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{

        NSLog(@"test --- sync start");
        sleep(5);
        NSLog(@"test --- sync end");
    });
    NSLog(@"哈哈哈哈哈哈哈");
    // test --- sync start    test --- sync end    哈哈哈哈哈哈哈
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{

        NSLog(@"test --- sync start");
        sleep(5);
        NSLog(@"test --- sync end");
    });
    NSLog(@"哈哈哈哈哈哈哈");
    // test --- sync start  哈哈哈哈哈哈哈   test --- sync end    
使用場景:

​ 執行Main Dispatch Queue時,使用另外的線程Global Dispatch Queue進行處理,處理結束後立即使用所得到的結果。在這種情況下就要使用dispatch_sync函數

​ dispatch_sync使用簡單,也容易引起死鎖

​ 如在主線程執行以下源代碼就會死鎖: 該源代碼在主線程執行的Block,並等待其執行結束,而其實在主線程正在執行這些源代碼,所以無法執行追加到主線程的Block

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

dispatch_apply

dispatch_apply是dispatch_sync和Dispatch Queue的關聯API,該函數按指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理執行結束

​ 輸出結果中最後的done必定在最後的位置上。這是因爲dispatch_apply函數會等待全部處理執行結束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
    NSLog(@"done");

// 推薦用法
dispatch_asynce(queue, ^{
  dispatch_apply(10, queue,....
  
});

由於dispatch_apply函數會等待全部處理執行結束,所以推薦在dispatch_async函數中"非同步"地執行dispatch_apply函數

dispatch_suspend/dispatch_resume

dispatch_suspend: 掛起指定的dispatch Queue

dispatch_resume: 恢復指定的dispatch queue

掛起後,追加到dispatch queue中但尚未執行的處理在此之後停止執行,而恢復則使得這些處理能夠繼續執行。

Dispatch Semaphore

Dispatch Semaphore是持有計數的信號

dispatch_semaphore_wait函數等待Dispatch Semaphore的計數值達到大於或等於1

dispatch_semaphore_signal將Dispatch Semaphore的計數值+1

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
   
    dispatch_async(queue, ^{
       
         dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"test --- async start");
        sleep(5);
        NSLog(@"test --- async end");
        dispatch_semaphore_signal(semaphore);
    });
     // 會讓當前線程停止
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
       
        NSLog(@"111test --- async start");
        sleep(5);
        NSLog(@"111test --- async end");
        dispatch_semaphore_signal(semaphore);
    });

dispatch_once

dispatch I/O

如想提高文件讀取速度,可以嘗試使用Dispatch I/O

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章