44.通過Dispatch Group機制,根據系統資源狀況來執行任務

《編寫高質量iOS與OS X代碼的52個有效方法》--第六章 第44條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)


第44條:通過Dispatch Group機制,根據系統資源狀況來執行任務

dispatch group是GCD的一項特性,能夠把任務分組。調用者可以等待這組任務執行完畢,也可以在提供回調函數之后繼續往下執行,這組任務完成時,調用者會得到通知。這個功能有許多用途,其中最重要、最值得注意的用法,就是把將要并發執行的多個任務合為一組,于是調用者就可以知道這些任務何時才能全部執行完畢。

下面這個函數可以創建dispatch group:

dispatch_group_t group = dispatch_group_create();

dispatch group就是個簡單的數據結構,這種結構彼此之間沒什么區別,它不像派發隊列,后者還有個用來區別身份的標識符。想把任務編組,有兩種辦法。第一種是下面這個函數:

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);

它是普通的dispatch_async函數的變體,比原來多一個函數,用于表示待執行的塊所歸屬的組。還有種辦法能夠指定任務所屬的dispatch group,那就是下面這一對函數:

dispatch_group_enter(dispatch_group_t group)
dispatch_group_leave(dispatch_group_t group)

前者能夠使分組里正要執行的任務數遞增,而后者則使之遞減。由此可知,調用了dispatch_group_enter以后,必須又與之對應的dispatch_group_leave才行。這與引用計數(參見第29條)相似,要使用引用計數。就必須令保留操作與釋放操作彼此對應,以防內存泄漏。

下面這個函數可用于等待dispatch group執行完畢:

void dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

此函數接受兩個參數,一個是要等待的group,另一個是代表等待時間的timeout值。timeout參數表示函數在等待dispatch group執行完畢時,應該阻塞多久。如果執行dispatch group所需的時間小于timeout,則返回0,否則返回非0值。此參數也可以取常量DISPATCH_TIME_FOREVER,這表示函數會一直等待dispatch group執行完,而不會超時(time out)。
除了可以用上面那個函數等待dispatch group執行完畢之外,也可以換個辦法,使用下列函數:

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue,
                           dispatch_block_t block);

與wait函數略有不同的是:開發者可以向此函數傳入塊,等dispatch group執行完畢之后,塊會在特定的線程上執行。加入當前此案成不應阻塞,而開發者又想在那些任務全部完成時得到通知,那么此做法就很有必要了。

如果想令數組中的每個對象都執行某項任務,并且想等待所有任務執行完畢,那么就可以使用這個GCD特性來實現。代碼如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t dispatchGroup = dispatch_group_create();

NSArray * collection;
for (id object in collection) {
    dispatch_group_async(dispatchGroup, queue, ^{
        [object description];
    });
}

dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks

若當前線程不應阻塞,則可用notify函數來取代wait:

dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
    //Continue processing after completing tasks
});

notify回調時所選用的隊列,完全應該根據具體情況來定。筆者在范例代碼中使用了主線程隊列,這種是常見寫法。也可以用自定義的串行隊列或者全局并發隊列。

在本例中,所有任務都派發到同一隊列之中。但實際上未必一定要這樣做。也可以把某些任務放在優先級高的線程上執行,同時仍然把所有任務都歸入同一個dispatch group,并在執行完畢時獲得通知:

dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_group_t dispatchGroup = dispatch_group_create();

NSArray * lowPriorityObject;
NSArray * highPriorityObject;
for (id object in lowPriorityObject) {
    dispatch_group_async(dispatchGroup, lowPriorityQueue, ^{
        [object description];
    });
}

for (id object in highPriorityObject) {
    dispatch_group_async(dispatchGroup, highPriorityQueue, ^{
        [object description];
    });
}


dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
    //Continue processing after completing tasks
});

除了像上面這樣把任務提交到并發隊列之外,也可以把任務提交至各個串行隊列中,并用dispatch group跟蹤其執行狀況。然而,如果所有任務都排在同一個串行隊列里面,那么dispatch group就用處不大了。因為此時,任務總要逐個執行,所以只需在提交完全部任務之后再提交一個塊即可,這樣做與通過notify函數等待dispatch group執行完畢后再回調塊是等效的:

dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);

NSArray *collection;
for (id object in collection) {
    dispatch_async(queue, ^{
        [object description];
    });
}

dispatch_async(queue, ^{
    //Continue processing after completing tasks
});

筆者為何要在標題中談到“根據系統資源狀況來執行任務”呢?回頭看看向并發隊列派發任務的那個例子,就會明白了。為了執行隊列中的塊,GCD會在適當的時機自動創建新線程或或復用舊線程。如果并發隊列,那么其中有可能會有多個線程,這也就意味著多個塊可以并發執行。在并發隊列中,執行任務所用的并發線程數量,取決于各個因素,而GCD主要是根據系統資源狀況來判定這些因素的。加入CPU有多個核心,并且隊列中有大量任務等待執行,那那么GCD就可能會給該隊列配備多個線程。通過dispatch group所提供的這種簡便方式,既可以并發執行一系列給定的任務,又能在全部任務結束時得到通知。由于GCD有并發隊列機制,所以能夠根據可用的系統資源狀況來并發執行任務。

在前面的范例代碼中,我們遍歷某個collection,并在其每個元素上執行任務,而這也可以用另外一個GCD函數來實現:

void dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t));

此函數將塊反復執行一定的次數,每次傳給塊的參數值都會遞增,從0開始,直至“iterations-1”。其用法如下:

dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
dispatch_apply(10, queue, ^(size_t i) {
    //Perform task
});

有一件事要注意:dispatch_apply所用的隊列可以是并發隊列。如果采用并發隊列,那么系統就可以根據資源狀況來并行執行這些塊了,這與使用dispatch group的那段范例代碼一樣。

這再次表明,未必總要使用dispatch group。然而,dispatch_apply會持續阻塞,直到所有任務都執行完畢為止。由此可見,假如把塊派給了當前隊列(或者體系中高于當前隊列的某個串行隊列),就將導致死鎖。若想在后臺執行任務,則應使用dispatch group。

要點

  • 一系列任務可歸入一個dispatch group之中。開發者可以在這組任務執行完畢時獲得通知。
  • 通過dispatch group,可以在并發式派發隊列里同時執行多項任務。此時,GCD會根據系統資源狀況來調度這些并發執行的任務。開發者若自己來實現此功能,則需編寫大量代碼。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容