GCD介紹(四):完結(jié)
Dispatch Queue掛起
dispatch queue可以被掛起和恢復(fù)。使用 dispatch_suspend
函數(shù)來掛起,使用 dispatch_resume
函數(shù)來恢復(fù)。這兩個函數(shù)的行為是如你所愿的。另外,這兩個函數(shù)也可以用于dispatch source。
一個要注意的地方是,dispatch queue的掛起是block粒度的。換句話說,掛起一個queue并不會將當前正在執(zhí)行的block掛起。它會允許當前執(zhí)行的block執(zhí)行完畢,然后后續(xù)的block不再會被執(zhí)行,直至queue被恢復(fù)。
還有一個注意點:從man頁上得來的:如果你掛起了一個queue或者source,那么銷毀它之前,必須先對其進行恢復(fù)。
Dispatch Queue目標指定
所有的用戶隊列都有一個目標隊列概念。從本質(zhì)上講,一個用戶隊列實際上是不執(zhí)行任何任務(wù)的,但是它會將任務(wù)傳遞給它的目標隊列來執(zhí)行。通常,目標隊列是默認優(yōu)先級的全局隊列。
用戶隊列的目標隊列可以用函數(shù) dispatch_set_target_queue
來修改。我們可以將任意dispatch queue傳遞給這個函數(shù),甚至可以是另一個用戶隊列,只要別構(gòu)成循環(huán)就行。這個函數(shù)可以用來設(shè)定用戶隊列的優(yōu)先級。比如我們可以將用戶隊列的目標隊列設(shè)定為低優(yōu)先級的全局隊列,那么我們的用戶隊列中的任務(wù)都會以低優(yōu)先級執(zhí)行。高優(yōu)先級也是一樣道理。
有一個用途,是將用戶隊列的目標定為main queue。這會導致所有提交到該用戶隊列的block在主線程中執(zhí)行。這樣做來替代直接在主線程中執(zhí)行代碼的好處在于,我們的用戶隊列可以單獨地被掛起和恢復(fù),還可以被重定目標至一個全局隊列,然后所有的block會變成在全局隊列上執(zhí)行(只要你確保你的代碼離開主線程不會有問題)。
還有一個用途,是將一個用戶隊列的目標隊列指定為另一個用戶隊列。這樣做可以強制多個隊列相互協(xié)調(diào)地串行執(zhí)行,這樣足以構(gòu)建一組隊列,通過掛起和暫停那個目標隊列,我們可以掛起和暫停整個組。想象這樣一個程序:它掃描一組目錄并且加載目錄中的內(nèi)容。為了避免磁盤競爭,我們要確定在同一個物理磁盤上同時只有一個文件加載任務(wù)在執(zhí)行。而希望可以同時從不同的物理磁盤上讀取多個文件。要實現(xiàn)這個,我們要做的就是創(chuàng)建一個dispatch queue結(jié)構(gòu),該結(jié)構(gòu)為磁盤結(jié)構(gòu)的鏡像。
首先,我們會掃描系統(tǒng)并找到各個磁盤,為每個磁盤創(chuàng)建一個用戶隊列。然后掃描文件系統(tǒng),并為每個文件系統(tǒng)創(chuàng)建一個用戶隊列,將這些用戶隊列的目標隊列指向合適的磁盤用戶隊列。最后,每個目錄掃描器有自己的隊列,其目標隊列指向目錄所在的文件系統(tǒng)的隊列。目錄掃描器枚舉自己的目錄并為每個文件向自己的隊列提交一個block。由于整個系統(tǒng)的建立方式,就使得每個物理磁盤被串行訪問,而多個物理磁盤被并行訪問。除了隊列初始化過程,我們根本不需要手動干預(yù)什么東西。
信號量
dispatch的信號量是像其他的信號量一樣的,如果你熟悉其他多線程系統(tǒng)中的信號量,那么這一節(jié)的東西再好理解不過了。
信號量是一個整形值并且具有一個初始計數(shù)值,并且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數(shù)會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數(shù)器大于零,然后線程會減少這個計數(shù)。
我們使用函數(shù) dispatch_semaphore_create
來創(chuàng)建dispatch信號量,使用函數(shù) dispatch_semaphore_signal
來信號通知,使用函數(shù) dispatch_semaphore_wait
來等待。這些函數(shù)的man頁有兩個很好的例子,展示了怎樣使用信號量來同步任務(wù)和有限資源訪問控制。
單次初始化
GCD還提供單詞初始化支持,這個與pthread中的函數(shù) pthread_once
很相似。GCD提供的方式的優(yōu)點在于它使用block而非函數(shù)指針,這就允許更自然的代碼方式:
這個特性的主要用途是惰性單例初始化或者其他的線程安全數(shù)據(jù)共享。典型的單例初始化技術(shù)看起來像這樣(線程安全的):
- + (id)sharedWhatever
- {
- static Whatever *whatever = nil;
- @synchronized([Whatever class])
- {
- if(!whatever)
- whatever = [[Whatever alloc] init];
- }
- return whatever;
- }
這挺好的,但是代價比較昂貴;每次調(diào)用 +sharedWhatever
函數(shù)都會付出取鎖的代價,即使這個鎖只需要進行一次。確實有更風騷的方式來實現(xiàn)這個,使用類似雙向鎖或者是原子操作的東西,但是這樣挺難弄而且容易出錯。
使用GCD,我們可以這樣重寫上面的方法,使用函數(shù) dispatch_once
:
- + (id)sharedWhatever
- {
- static dispatch_once_t pred;
- static Whatever *whatever = nil;
- dispatch_once(&pred, ^{
- whatever = [[Whatever alloc] init];
- });
- return whatever;
- }
這個稍微比 @synchronized
方法簡單些,并且GCD確保以更快的方式完成這些檢測,它保證block中的代碼在任何線程通過 dispatch_once
調(diào)用之前被執(zhí)行,但它不會強制每次調(diào)用這個函數(shù)都讓代碼進行同步控制。實際上,如果你去看這個函數(shù)所在的頭文件,你會發(fā)現(xiàn)目前它的實現(xiàn)其實是一個宏,進行了內(nèi)聯(lián)的初始化測試,這意味著通常情況下,你不用付出函數(shù)調(diào)用的負載代價,并且會有更少的同步控制負載。
結(jié)論
這一章,我們介紹了dispatch queue的掛起、恢復(fù)和目標重定,以及這些功能的一些用途。另外,我們還介紹了如何使用dispatch 信號量和單次初始化功能。到此,我已經(jīng)完成了GCD如何運作以及如何使用的介紹。