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



















