Dispatch queues
Dispatch queue有兩種類型,一種是線性queue,線性queue一個(gè)一個(gè)的執(zhí)行queue上的任務(wù),如果當(dāng)前任務(wù)正在執(zhí)行,queue一直等待在那里直它完成才會(huì)開始新的任務(wù);另一種是并發(fā)queue,它并不等待正在執(zhí)行的任務(wù)的完成,只要有資源讓它開啟任務(wù),它就會(huì)分配queue上任務(wù)的執(zhí)行。
提交到線性queue上的Blocks是按先進(jìn)先出(FIFO)的順序執(zhí)行的。但要注意的是,不同線性queue上的Block的執(zhí)行是相互獨(dú)立的,所以互相之間是可能并發(fā)執(zhí)行的。提交到并發(fā)queue上的Block也是按FIFO的順序出隊(duì)列的,因?yàn)椴l(fā)queue并不等待任務(wù)的完成,所以只要有資源可運(yùn)行并發(fā)queue就會(huì)以FIFO的順序開啟queue上的block的執(zhí)行,block之間可能是并發(fā)的。
當(dāng)你把任務(wù)提交到并發(fā)queue上,可并發(fā)執(zhí)行的任務(wù)數(shù)是由系統(tǒng)的當(dāng)前狀態(tài)決定的。iOS或MacOS根據(jù)當(dāng)前的狀態(tài)(比如并發(fā)queue上的任務(wù)數(shù)、CPU核數(shù)和CPU的使用情況)來決定并發(fā)數(shù)。
如果你的任務(wù)應(yīng)該有序地執(zhí)行,或者不應(yīng)該并發(fā)的執(zhí)行,那么你應(yīng)該用線性queue。
獲取 Dispatch Queues
獲取Dispatch queue有兩種方法
dispatch_queue_create
dispatch_get_main_queue/dispatch_get_global_queue
dispatch_queue_create
用此方法創(chuàng)建新的dispatch queue。
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("myUniqueIdentity", NULL);
上面你創(chuàng)建了一個(gè)新的線性queue。 dispatch_queue_create
, 有兩個(gè)參數(shù):
參數(shù) | 描述 |
---|---|
label | label 唯一標(biāo)識(shí)你創(chuàng)建的queue,label在debug時(shí)(Instruments, sample, stackshots, crash reports)會(huì)有用.因?yàn)橄到y(tǒng)庫(kù)或其它框架也會(huì)創(chuàng)建queue,為了保證label的唯一,請(qǐng)DNS的反轉(zhuǎn)格式(com.example.myqueue)。這個(gè)參數(shù)是可選的,可以為NULL |
attr | OS X v10.7 , iOS 4.3及其之后版本, DISPATCH_QUEUE_SERIAL (或者 NULL) 來創(chuàng)建線性queue;DISPATCH_QUEUE_CONCURRENT 來創(chuàng)建并發(fā)queue. 更早版本, 此參數(shù)只能為NULL. |
所以你可以如下所示來創(chuàng)建并發(fā)queue:
dispatch_queue_t concurrentQueue = dispatch_queue_create("myAnotherUniqueIdentify", DISPATCH_QUEUE_CONCURRENT);
另外,當(dāng)一個(gè)線性queue被創(chuàng)建且有任務(wù)提交上去,系統(tǒng)會(huì)為每個(gè)線性queue創(chuàng)建一個(gè)線程。如果你創(chuàng)建了2000個(gè)線性queue,就會(huì)有相應(yīng)的2000個(gè)線程被創(chuàng)建。太多的線程會(huì)增加內(nèi)存的消耗,以及線程間的切換都會(huì)大大降低系統(tǒng)性能。所以不要濫用線性queue,只有當(dāng)任務(wù)之間的執(zhí)行是有序的或者為了避免多線程并發(fā)而引起的資源競(jìng)爭(zhēng)時(shí),才使用線性queue。而且線性queue的個(gè)數(shù)應(yīng)該和你需求的一至,不要?jiǎng)?chuàng)建多余的線性queue。 如是任務(wù)沒有數(shù)據(jù)一至性問題,且可以并發(fā),請(qǐng)用并發(fā)queue,而不是用多個(gè)線性queue來并發(fā)。 因?yàn)椴l(fā)queue使用的線程是由系統(tǒng)內(nèi)核非常有效地管理的,用并發(fā)queue更高效。
雖然,編譯器有強(qiáng)大的內(nèi)存自動(dòng)管理(ARC),但是GCD中創(chuàng)建的實(shí)例(這里不是Objective-C對(duì)象,而是GCD中一些結(jié)構(gòu)體實(shí)例)須由我們自己手動(dòng)釋放,因?yàn)檫@些實(shí)例(如dispatch queue)不像Block那樣被當(dāng)成Objective-C對(duì)象。當(dāng)你使用完這些實(shí)例時(shí),應(yīng)用dispatch_release
釋放它們,用dispatch_retain
來?yè)碛兴鼈儭?GCD函數(shù)中含有"create"的,往往都需要我們dispatch_release
。
dispatch_release(mySerialDispatchQueue);
dispatch_retain(myConcurrentDispatchQueue);
那么,看下面代碼:
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create( "com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on myConcurrentDispatchQueue");});
dispatch_release(myConcurrentDispatchQueue);
上例中,往并發(fā)queue提交了任務(wù)之后,馬上就釋放它。這樣做安全嗎?
這樣沒任何問題。當(dāng)一個(gè)Block被提交到一個(gè)queue上時(shí),這個(gè)Block會(huì)擁有這個(gè)queue(dispatch_retain, 即,使queue引用記數(shù)加1)。當(dāng)Block執(zhí)行結(jié)束時(shí)它會(huì)釋放對(duì)queue的擁有權(quán)(dispatch_release, 即,使queue引用記數(shù)減1)。所以,即使提交block之后立馬釋放queue,queue也不會(huì)被系統(tǒng)回收,而Block也可以被執(zhí)行。當(dāng)Block執(zhí)行完并釋放queue的擁有權(quán)時(shí),queue才真正被回收。
Main Dispatch Queue / Global Dispatch Queue
Main Dispatch Queue / Global Dispatch Queue 由系統(tǒng)提供。
Main dispatch queue, 由名字也可以知道, 它將任務(wù)分配到主線程上執(zhí)行。Main dispatch queue是線性queue,且總共只有一個(gè)。因?yàn)镸ain dispatch queue在主線程上執(zhí)行任務(wù),所以你應(yīng)該把那些必須由主線程執(zhí)行的任務(wù)(如UI更新)提交到此queue上。performSelectorOnMainThread
也有相似的功能。
如果不是有特殊需求,一般而言,你不需需用dispatch_queue_create
創(chuàng)建自己的并發(fā)queue,系統(tǒng)提供的全局queue(gloabl queue)足已。 Global queue有四種分別對(duì)應(yīng)四個(gè)不同優(yōu)先級(jí): high, default, low, and background.
Types of dispatch queues
Name | Type | Description |
---|---|---|
Main dispatch queue | Serial dispatch queue | Executed on the main thread |
Global dispatch queue (High priority) | Concurrent dispatch queue | Priority: High (Utmost priority) |
Global dispatch queue (Default priority) | Concurrent dispatch queue | Priority: Normal |
Global dispatch queue (Low priority) | Concurrent dispatch queue | Priority: Low |
Global dispatch queue (Background priority) | Concurrent dispatch queue | Priority: Background |
下面展示了不同queue的獲取方法:
/*
* How to get the main dispatch queue */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* How to get a global dispatch queue of high priority
*/
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* How to get a global dispatch queue of default priority
*/
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* How to get a global dispatch queue of low priority
*/
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* How to get a global dispatch queue of background priority
*/
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
隨帶提一下, 如果你對(duì)系統(tǒng)提供的queue,進(jìn)行 dispatch_retain
或者 dispatch_release
, 什么都不會(huì)發(fā)生,并不會(huì)增加或減少queue的引用計(jì)數(shù). 毫無疑問,使用系統(tǒng)提供的queue,比你自己創(chuàng)建queue理方便。
使用dispatch queue例子:
/*
* Execute a Block on a global dispatch queue of default priority.
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*
* some tasks here to be executed concurrently
*/
/*
* Then, execute a Block on the main dispatch queue
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* Here, some tasks that can work only on the main thread.
*/
});
});
Controlling Dispatch Queues
GCD提供了大量有用的API讓我們管理dispatch queue上的任務(wù)。
讓我們一個(gè)一個(gè)查看這些API有多么強(qiáng)大!
dispatch_set_target_queue
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
函數(shù)dispatch_set_target_queue
可以給你的queue設(shè)置一個(gè)target queue。這主要用在給你新建的queue指定優(yōu)先級(jí)。無論你用dispatch_queue_create
創(chuàng)建的是線性queue(serial queue)還是并發(fā)queue(concurrent queue),它的優(yōu)先級(jí)都與global queue的默認(rèn)優(yōu)先級(jí)相同。在創(chuàng)建queue之后,你就可以用這個(gè)函數(shù)來改變它的優(yōu)先級(jí)。下面代碼展示了如何讓一個(gè)線性queue擁有background優(yōu)先級(jí)。
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);
dispatch_set_target_queue
的第一個(gè)參數(shù)是你要設(shè)置的queue,第二個(gè)參數(shù)是target queue,這兩個(gè)參數(shù)都不能為NULL。它使mySerialDispatchQueue的優(yōu)先級(jí)和target queue(globalDispatchQueueBackground)一樣。不要任何系統(tǒng)的queue(main queue和global queue)作為第一個(gè)參數(shù)傳進(jìn)去,這樣做的結(jié)果是不可預(yù)料的!
dispatch_set_target_queue
不僅可以用來改變queue的優(yōu)先級(jí),還可以創(chuàng)建queue 的層級(jí)關(guān)系。如果你把一個(gè)任務(wù)(block)提交到一個(gè)線性queue(A)上,同時(shí)這個(gè)線性queue的target queue是另一個(gè)線性queue(B)。那么這個(gè)任務(wù)(block)將不會(huì)和提交到B或其它以B為target queue的queue上的block ,并發(fā)執(zhí)行。
如圖所示,底下三個(gè)dispatch queue上的任務(wù)(blocks)將會(huì)線性執(zhí)行。當(dāng)你的各個(gè)任務(wù)不應(yīng)該并發(fā)執(zhí)行,同時(shí)又必須放在不同的queue上時(shí);你就可以通過設(shè)置這些queue的target都為某個(gè)線性queue,來阻止并發(fā)。實(shí)際上,我自己一直想不出有類似需求的場(chǎng)景。
dispatch_after
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
用dispatch_after
來設(shè)置queue上某個(gè)任務(wù)(block)的啟動(dòng)時(shí)間。下面代碼做的是,延遲三秒后將一個(gè)block添加到main queue上:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds.");
});
ull是C語(yǔ)言中指定unsigned long long類型的。注意的是,此函數(shù)并不是在指定時(shí)間之后執(zhí)行block,而是在指定時(shí)間之后將block添加到目標(biāo)queue中。你可以把該函數(shù)看作是增強(qiáng)版的dispatch_async
,當(dāng)指定延遲時(shí)間是DISPATCH_TIME_NOW
時(shí),此函數(shù)功能上與dispatch_async
等價(jià),不過蘋果官方文檔建議你應(yīng)該用dispatch_async
,而延遲時(shí)間為DISPATCH_TIME_FOREVER
時(shí),結(jié)果是不可預(yù)料的:
Passing DISPATCH_TIME_NOW as the when parameter is supported, but is not as optimal as calling dispatch_async instead. Passing DISPATCH_TIME_FOREVER is undefined.
dispatch_after
并不適合執(zhí)行高精度要求的延時(shí)任務(wù),它對(duì)于那些精度不是那么高的延時(shí)任務(wù)是非常有用的。函數(shù)的三個(gè)參數(shù)中, 這里只講第一個(gè)參數(shù)time吧。首先你要用dispatch_time
或dispatch_walltime
來創(chuàng)建time。此函數(shù)接收納秒數(shù),所以你應(yīng)該用NSEC_PER_SEC
(一秒的納秒數(shù))或NSEC_PER_MSEC
, 類似的常量如:
#define NSEC_PER_SEC 1000000000ull //一秒的納秒數(shù)
#define USEC_PER_SEC 1000000ull //一秒的微秒數(shù)
#define NSEC_PER_USEC 1000ull //一微秒的納秒數(shù)
#define NSEC_PER_MSEC 1000000ull //一毫秒的納秒數(shù)
要延遲一秒,你可以這樣創(chuàng)建:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
要延遲150毫秒,你可以這樣:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
dispatch_time
主要用來創(chuàng)建相對(duì)時(shí)間,而dispatch_walltime
則用來創(chuàng)建絕對(duì)時(shí)刻。比如,你用dispatch_walltime
來創(chuàng)建2016年11月11號(hào)11點(diǎn)11分11秒時(shí)刻。可以用dispatch_walltime
來實(shí)現(xiàn)鬧鐘,不過此方法精度不高。dispatch_walltime
根據(jù)結(jié)構(gòu)體timespec來創(chuàng)建時(shí)刻的,如下面例子:
dispatch_time_t getDispatchTimeByDate(NSDate *date) {
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
Dispatch Group
Dispatch group 用來對(duì)block分組。當(dāng)你有個(gè)任務(wù),并且些任務(wù)要求其它任務(wù)都完成時(shí)才能開始,這時(shí)你就可以和Dispatch Group來實(shí)現(xiàn)。當(dāng)然簡(jiǎn)單的情況下,你可以把任務(wù)都放進(jìn)一個(gè)線性queue中,在queue尾放入那個(gè)任務(wù)。但是如果遇到并發(fā)隊(duì)列或有多個(gè)隊(duì)列情況時(shí),就變得很復(fù)雜,dispatch group應(yīng)此而生。下因代碼例子將三個(gè)block分到一個(gè)組,并在這些block都被執(zhí)行之后,執(zhí)行main queue上的block:
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(@"blok0");});
dispatch_group_async(group, queue, ^{NSLog(@"blok1");});
dispatch_group_async(group, queue, ^{NSLog(@"blok2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done")});
dispatch_release(group);
結(jié)果將是(前三個(gè)輸出順序不定):
blk1
blk2
blk0
done
首先我們用dispatch_group_create
創(chuàng)建dispatch_group_t實(shí)例。因?yàn)楹瘮?shù)中有"create",當(dāng)不再需要它時(shí)你必須release它, GCD中所有的release都用dispatch_release
。函數(shù)dispatch_group_async
就像dispatch_async
把block添加到隊(duì)列中,唯一的區(qū)別是多了一個(gè)參數(shù)dispatch_group_t,將block和dispatch group關(guān)聯(lián)起來。當(dāng)block和某個(gè)dispatch group關(guān)聯(lián)之后,block會(huì)擁有這個(gè)dispatch group,(dispatch_retain,就如同block被添加到queue中時(shí)會(huì)retain這個(gè)queue一樣),當(dāng)block執(zhí)行完時(shí),block會(huì)釋放它擁有的dispatch group(dispatch_release). 當(dāng)所有與dispatch group相關(guān)聯(lián)的block執(zhí)行完時(shí),dispatch_group_notify
會(huì)將block("done")添加到main queue上。dispatch_group_notify
并不會(huì)阻塞當(dāng)前線程,它監(jiān)聽指定的dispach group,當(dāng)該group上所有block都執(zhí)行時(shí),它將block添加到指定queue上。如果你要等待group上的所有block完成,你可以使用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, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_wait(group, GISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_group_wait
的第二個(gè)參數(shù)是timeout(dispatch_time_t)指定等待的時(shí)長(zhǎng), 如果在timeout時(shí)長(zhǎng)內(nèi)group上的任務(wù)都完成則返回0(success), 否則返回非零(failed)。timeout參數(shù)也像dispatch_after
那樣:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
//所有與group相關(guān)聯(lián)的任務(wù)都已完成
} else {
//與group相關(guān)聯(lián)的任務(wù)中還有在運(yùn)行
}
當(dāng)dispatch_group_wait
被調(diào)用時(shí),并不會(huì)從該函數(shù)立即返回,當(dāng)前線程阻塞在此函數(shù)上,等待group上所有任務(wù)完成,或者timeout。
你可以用DISPATCH_TIME_NOW
來查看group上的任務(wù)是否都已完成:
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW)
if (result == 0) {
//此時(shí)group上任務(wù)都已完成
} else {
//此時(shí)group上還有任務(wù)在運(yùn)行
}
在關(guān)聯(lián)block和group時(shí),dispatch_group_async
并不是唯一的函數(shù),還有dispatch_group_enter
(與之成對(duì)的是dispatch_group_leave
)。先看例子:
dispatch_group_t group1 = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_group_enter(group1);
NSLog("group1 queue blk0");
dispatch_group_leave(group1);
});
dispatch_async(queue, ^{
dispatch_group_enter(group1);
dispatch_group_enter(group2);
NSLog("group1 group2 queue blk1");
dispatch_group_leave(group1);
dispatch_group_leave(group2);
});
dispatch_async(mainQueue, ^{
dispatch_group_enter(group2);
NSLog("group2 mainQueue, blk2");
dispatch_group_leave(group2);
});
dispatch_async(mainQueue, ^{
dispatch_group_enter(group1);
dispatch_group_enter(group2);
NSLog("group1 group2 mainQueue blk3");
dispatch_group_leave(group1);
dispatch_group_leave(group2);
});
/*
* 監(jiān)測(cè)group1。當(dāng)blk0,blk1,blk3都完成時(shí),添加block到queue
*/
dispatch_group_notify(group1, queue, ^{
NSLog("blk0, blk1, blk3, all have finished!");
});
/*
* 監(jiān)測(cè)group2, 當(dāng)blk1, blk2, blk3 都完成時(shí)添加block到mainQueue
*/
dispatch_group_notify(group2, mainQueue, ^{
NSLog("blk1, blk2, blk3, all have finished!");
});
注意dispatch_group_enter
和dispatch_group_leave
必須保持平衡(成對(duì)出現(xiàn))。如果你需要把一個(gè)block關(guān)聯(lián)到不同的group上,你只能使用dispatch_group_enter
函數(shù)對(duì),否則使用dispatch_group_async
更方便一點(diǎn)。
下面看代碼是AFNetWorking中的例子:
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.completionGroup) {
dispatch_group_enter(self.completionGroup);
}
dispatch_async(http_request_operation_processing_queue(), ^{
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
}
}
if (self.completionGroup) {
dispatch_group_leave(self.completionGroup);
}
});
};
#pragma clang diagnostic pop
}
dispatch_barrier_async
void dispatch_barrier_async( dispatch_queue_t queue, dispatch_block_t block);
函數(shù)只能兩個(gè)參數(shù),一個(gè)是queue,一個(gè)block。作用是把block加到queue上,特殊的地方在于,被加queue上的block稱為barrier block。barrier block 把queue上的任務(wù)的執(zhí)行分成三個(gè)階段:
- 將先于barrier block前提交到queue上的所有block執(zhí)行完
- 執(zhí)行barrier block
- 只有當(dāng)barrier block執(zhí)行好后,才會(huì)執(zhí)行后于barrier block添加到queue上的block
這里的參數(shù)queue,應(yīng)該是你通過dispatch_queue_create
創(chuàng)建的并發(fā)queue,如果你傳入的是線性queue或者全局并發(fā)queue,函數(shù)作用就和dispatch_queue_async
一樣。
來個(gè)例子,具體看看:
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);
我們要實(shí)現(xiàn)這樣的功能,在洗澡的時(shí)候同時(shí)洗臉?biāo)⒀老茨槨H缓笞鐾晟鲜鲞@些事之后(blk0),吃早飯(blk1),吃完早飯騎自行車出去(邊騎邊聽音樂).很明顯,上述代碼并不符合需求,它將所有事件都并發(fā)了。
有了dispatch_barrier_async
我們可以這樣做:
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_barrier_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);
對(duì)數(shù)據(jù)庫(kù)或文件進(jìn)行寫操作時(shí),我們也可以用此函數(shù)來完成:
// read operation added to queue before barrier.(May added on different thread)
dispatch_async(queue, blk0_read1);
dispatch_async(queue, blk0_read2);
dispatch_async(queue, blk0_read3);
...
dispatch_barrier_async(queue, blk1_write);
// read operation added to queue after barrier.(May added on different thread)
dispatch_async(queue, blk2_read1);
dispatch_async(queue, blk2_read2);
dispatch_async(queue, blk2_read3);
...
上面兩處示例代碼的執(zhí)行順序是這樣的:blk0 -> blk1 -> blk2(blk0, blk2為前綴的block各自之間分別異步)。
dispatch_apply
void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t));
把block添加到queue上iterations次,并且等待所有添加的block執(zhí)行完成。
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");
結(jié)果可能是:
4
1
0
3
5
7
8
9
2
6
done
上面結(jié)果數(shù)字的排列順序是不定的。
再看一個(gè)例子:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 在 global dispatch queue 上異步執(zhí)行 */
dispatch_async(queue, ^{
/*
* 在 global dispatch queue上, dispatch_apply 函數(shù)等待所有任務(wù)的完成
*/
dispatch_apply([array count], queue, ^(size_t index) {
/*
* 異步地對(duì)array上的對(duì)象進(jìn)行操作 */
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
/*
* dispatch_apply 所有的任務(wù)已完成*/
/*
* 在 main dispatch queue 上異步執(zhí)行 */
dispatch_async(dispatch_get_main_queue(), ^{
/*
* Executed on the main dispatch queue.
* Something like updating userface, etc. */
NSLog(@"done");
});
});
dispatch_once
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
用來確保在整個(gè)app的生命周期內(nèi)block只執(zhí)行一次。
不用dispatch_once:
static int initialized = NO;
if (initialized == NO) {
// do initializing
initialized = YES;
}
使用dispatch_once
顯得更優(yōu)雅:
static dispatch_once_t pred;
dispatch_once(&pred, ^{
// do the initializing.
});
第一種方法雖然在大多數(shù)情況下是沒問題的,但它是線程不安全的。比如A線程執(zhí)行到行3,即將執(zhí)行行4『initialized = YES;』。此時(shí)線程A的執(zhí)行權(quán)被剝奪,線程B獲得執(zhí)行權(quán),執(zhí)行這塊代碼時(shí),initailized仍舊為NO,所以又進(jìn)行了一次初始化。所以當(dāng)多線程情況下,第一種方法可能會(huì)被執(zhí)行多次。而dispatch_once
就沒問題,它是線程安全的。dispatch_once
經(jīng)常被用于創(chuàng)建單例。
dispatch_suspend/dispatch_resume
這兩個(gè)方法用來suspend或者resume dispatch queue的執(zhí)行。一個(gè)dispatch queue可以這樣被suspend(休眠):
dispatch_suspend(queue);
然后resume:
dispatch_resume(queue);
suspend并不會(huì)影響那些已經(jīng)在執(zhí)行的任務(wù)。它只會(huì)阻止還在queue的task的執(zhí)行。resume之后,queue上的任務(wù)又可以繼續(xù)被調(diào)度執(zhí)行。
你可以多次調(diào)用dispatch_suspend(queue);
每調(diào)用一次queue的計(jì)數(shù)加一,中要此計(jì)數(shù)大于0,queue就一直處于休眠中。所以請(qǐng)保持dispatch_suspend(queue);
和dispatch_resume(queue);
的平衡。
Dispatch Semaphore
Dispatch semaphore 相比傳統(tǒng)的信號(hào)量機(jī)制性能上更優(yōu)秀;只有當(dāng)線程需要阻塞時(shí),dispatch semaphore才會(huì)調(diào)用系統(tǒng)內(nèi)核;如果不會(huì)阻塞線程,則不會(huì)進(jìn)行系統(tǒng)內(nèi)核的調(diào)用。Dispatch semaphore也是通過計(jì)數(shù)來實(shí)現(xiàn)多線程中的控制的。當(dāng)counter大于0時(shí),程序繼續(xù)執(zhí)行;當(dāng)counter小于等于0,程序就等待在那里直到counter大于0,才繼續(xù)執(zhí)行。
相關(guān)函數(shù)就三個(gè):
/創(chuàng)建信號(hào)量變量
dispatch_semaphore_t dispatch_semaphore_create( long value);/
//Increment the counting semaphore. If the previous value was less than zero, this function wakes a thread currently waiting in dispatch_semaphore_wait.
//把信號(hào)量計(jì)數(shù)加1,如果之前信號(hào)量值小于等于0則喚醒一個(gè)由dispatch_semaphore_wait阻塞的線程
long dispatch_semaphore_signal( dispatch_semaphore_t dsema);
//Decrement the counting semaphore. If the resulting value is less than zero, this function waits in FIFO order for a signal to occur before returning.
//把信號(hào)量計(jì)數(shù)減1,如果減1后值小于0,此函數(shù)以先進(jìn)先出FIFO的順序阻塞等待singal。
//如果成功則返回0; 否則返回非0,表示timeout(timeout的時(shí)間過去了,還是沒收到singal)
long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
/*
* The counter of the dispatch semaphore was more than one.
* Or it became one and more before the specified timeout.
* The counter is automatically decreased by one.
*
* Here, you can execute a task that needs a concurrency control. */
} else {
/*
* Because the counter of the dispatch semaphore was zero, * it has waited until the specified timeout.
*/
}
應(yīng)用場(chǎng)景大致可歸為么兩類:
多個(gè)線程訪問有限資源
兩個(gè)線程之間相對(duì)某事件的同步
比如有兩個(gè)線程A和B,B的某一任務(wù)mession1執(zhí)行的前提條件是A完成A的任務(wù)mession2。當(dāng)執(zhí)行到B的mession1時(shí),如果A的mession2沒有完成,B就被阻塞在那里直到A完成mession1.
多個(gè)線程訪問有限資源
先看代碼:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) {
dispatch_async(queue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}
上面代碼在并發(fā)queue上更新array,即多個(gè)線程同時(shí)修改array,這就會(huì)使array變臟(corrupted)數(shù)據(jù)一至性問題,導(dǎo)至程序crash。我們可以用dispatch semaphore來防止多個(gè)線程同時(shí)修改array。
使用dispatch semaphore后,上面代碼應(yīng)是這樣的:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//你的資源數(shù)只有一個(gè)(即array),所以參數(shù)傳1;參數(shù)值應(yīng)該等于你的資源個(gè)數(shù)。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) {
dispatch_async(queue, ^{
//等待直到semphore大于0
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
//完成任務(wù),釋放資源的控制,使別的線程也能訪問。
dispatch_semaphore_signal(semaphore);
});
}
dispatch_release(semaphore);
兩個(gè)線程之間相對(duì)某事件的同步
看代碼
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
void (^blk1)(void) = ^{
NSLog("Mum give a life to me");
NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
blk2的事件("I grow up")依賴于blk1的事件("Mum give a life to me"),"I grow up"應(yīng)該在"Mum give a life to me"之后發(fā)生。但是上述代碼這兩個(gè)事件的執(zhí)行順序是不確定的。
使用semaphore代碼應(yīng)該是這樣:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
void (^blk1)(void) = ^{
NSLog("Mum give a life to me");
//事件發(fā)生,增加semaphore值
dispatch_semaphore_signal(semaphore);
NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
//如果事件沒有發(fā)生, semaphore為0,一直阻塞等待;如果事件發(fā)生,semaphore為1,繼續(xù)執(zhí)行
dispatch_semaphore_wait(semaphore);
NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_release(semaphore);