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