Dispatch queues
Dispatch queue有兩種類型,一種是線性queue,線性queue一個一個的執(zhí)行queue上的任務遇西,如果當前任務正在執(zhí)行,queue一直等待在那里直它完成才會開始新的任務杂瘸;另一種是并發(fā)queue,它并不等待正在執(zhí)行的任務的完成伙菊,只要有資源讓它開啟任務败玉,它就會分配queue上任務的執(zhí)行。
提交到線性queue上的Blocks是按先進先出(FIFO)的順序執(zhí)行的镜硕。但要注意的是运翼,不同線性queue上的Block的執(zhí)行是相互獨立的,所以互相之間是可能并發(fā)執(zhí)行的兴枯。提交到并發(fā)queue上的Block也是按FIFO的順序出隊列的血淌,因為并發(fā)queue并不等待任務的完成,所以只要有資源可運行并發(fā)queue就會以FIFO的順序開啟queue上的block的執(zhí)行财剖,block之間可能是并發(fā)的悠夯。
當你把任務提交到并發(fā)queue上,可并發(fā)執(zhí)行的任務數(shù)是由系統(tǒng)的當前狀態(tài)決定的躺坟。iOS或MacOS根據(jù)當前的狀態(tài)(比如并發(fā)queue上的任務數(shù)沦补、CPU核數(shù)和CPU的使用情況)來決定并發(fā)數(shù)。
如果你的任務應該有序地執(zhí)行咪橙,或者不應該并發(fā)的執(zhí)行夕膀,那么你應該用線性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)建了一個新的線性queue产舞。 dispatch_queue_create
, 有兩個參數(shù):
參數(shù) | 描述 |
---|---|
label | label 唯一標識你創(chuàng)建的queue,label在debug時(Instruments, sample, stackshots, crash reports)會有用.因為系統(tǒng)庫或其它框架也會創(chuàng)建queue菠剩,為了保證label的唯一易猫,請DNS的反轉(zhuǎn)格式(com.example.myqueue)。這個參數(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);
另外嘴办,當一個線性queue被創(chuàng)建且有任務提交上去瞬场,系統(tǒng)會為每個線性queue創(chuàng)建一個線程。如果你創(chuàng)建了2000個線性queue涧郊,就會有相應的2000個線程被創(chuàng)建贯被。太多的線程會增加內(nèi)存的消耗,以及線程間的切換都會大大降低系統(tǒng)性能妆艘。所以不要濫用線性queue彤灶,只有當任務之間的執(zhí)行是有序的或者為了避免多線程并發(fā)而引起的資源競爭時,才使用線性queue批旺。而且線性queue的個數(shù)應該和你需求的一至幌陕,不要創(chuàng)建多余的線性queue。 如是任務沒有數(shù)據(jù)一至性問題汽煮,且可以并發(fā)搏熄,請用并發(fā)queue,而不是用多個線性queue來并發(fā)暇赤。 因為并發(fā)queue使用的線程是由系統(tǒng)內(nèi)核非常有效地管理的心例,用并發(fā)queue更高效。
雖然鞋囊,編譯器有強大的內(nèi)存自動管理(ARC)止后,但是GCD中創(chuàng)建的實例(這里不是Objective-C對象,而是GCD中一些結(jié)構體實例)須由我們自己手動釋放溜腐,因為這些實例(如dispatch queue)不像Block那樣被當成Objective-C對象译株。當你使用完這些實例時,應用dispatch_release
釋放它們挺益,用dispatch_retain
來擁有它們歉糜。 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提交了任務之后黍檩,馬上就釋放它叉袍。這樣做安全嗎?
這樣沒任何問題刽酱。當一個Block被提交到一個queue上時喳逛,這個Block會擁有這個queue(dispatch_retain, 即,使queue引用記數(shù)加1)棵里。當Block執(zhí)行結(jié)束時它會釋放對queue的擁有權(dispatch_release, 即润文,使queue引用記數(shù)減1)姐呐。所以,即使提交block之后立馬釋放queue典蝌,queue也不會被系統(tǒng)回收曙砂,而Block也可以被執(zhí)行。當Block執(zhí)行完并釋放queue的擁有權時骏掀,queue才真正被回收鸠澈。
Main Dispatch Queue / Global Dispatch Queue
Main Dispatch Queue / Global Dispatch Queue 由系統(tǒng)提供。
Main dispatch queue, 由名字也可以知道, 它將任務分配到主線程上執(zhí)行截驮。Main dispatch queue是線性queue笑陈,且總共只有一個。因為Main dispatch queue在主線程上執(zhí)行任務葵袭,所以你應該把那些必須由主線程執(zhí)行的任務(如UI更新)提交到此queue上涵妥。performSelectorOnMainThread
也有相似的功能。
如果不是有特殊需求坡锡,一般而言蓬网,你不需需用dispatch_queue_create
創(chuàng)建自己的并發(fā)queue,系統(tǒng)提供的全局queue(gloabl queue)足已娜氏。 Global queue有四種分別對應四個不同優(yōu)先級: 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);
隨帶提一下, 如果你對系統(tǒng)提供的queue拳缠,進行 dispatch_retain
或者 dispatch_release
, 什么都不會發(fā)生,并不會增加或減少queue的引用計數(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上的任務绵疲。
讓我們一個一個查看這些API有多么強大哲鸳!
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設置一個target queue。這主要用在給你新建的queue指定優(yōu)先級盔憨。無論你用dispatch_queue_create
創(chuàng)建的是線性queue(serial queue)還是并發(fā)queue(concurrent queue)徙菠,它的優(yōu)先級都與global queue的默認優(yōu)先級相同。在創(chuàng)建queue之后郁岩,你就可以用這個函數(shù)來改變它的優(yōu)先級婿奔。下面代碼展示了如何讓一個線性queue擁有background優(yōu)先級。
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
的第一個參數(shù)是你要設置的queue问慎,第二個參數(shù)是target queue萍摊,這兩個參數(shù)都不能為NULL。它使mySerialDispatchQueue的優(yōu)先級和target queue(globalDispatchQueueBackground)一樣如叼。不要任何系統(tǒng)的queue(main queue和global queue)作為第一個參數(shù)傳進去冰木,這樣做的結(jié)果是不可預料的!
dispatch_set_target_queue
不僅可以用來改變queue的優(yōu)先級,還可以創(chuàng)建queue 的層級關系踊沸。如果你把一個任務(block)提交到一個線性queue(A)上歇终,同時這個線性queue的target queue是另一個線性queue(B)。那么這個任務(block)將不會和提交到B或其它以B為target queue的queue上的block 逼龟,并發(fā)執(zhí)行评凝。
如圖所示,底下三個dispatch queue上的任務(blocks)將會線性執(zhí)行审轮。當你的各個任務不應該并發(fā)執(zhí)行肥哎,同時又必須放在不同的queue上時;你就可以通過設置這些queue的target都為某個線性queue疾渣,來阻止并發(fā)。實際上崖飘,我自己一直想不出有類似需求的場景榴捡。
dispatch_after
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
用dispatch_after
來設置queue上某個任務(block)的啟動時間。下面代碼做的是朱浴,延遲三秒后將一個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語言中指定unsigned long long類型的吊圾。注意的是,此函數(shù)并不是在指定時間之后執(zhí)行block翰蠢,而是在指定時間之后將block添加到目標queue中项乒。你可以把該函數(shù)看作是增強版的dispatch_async
,當指定延遲時間是DISPATCH_TIME_NOW
時梁沧,此函數(shù)功能上與dispatch_async
等價檀何,不過蘋果官方文檔建議你應該用dispatch_async
,而延遲時間為DISPATCH_TIME_FOREVER
時廷支,結(jié)果是不可預料的:
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ù)的三個參數(shù)中恋拍, 這里只講第一個參數(shù)time吧垛孔。首先你要用dispatch_time
或dispatch_walltime
來創(chuàng)建time。此函數(shù)接收納秒數(shù)施敢,所以你應該用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)建相對時間僵娃,而dispatch_walltime
則用來創(chuàng)建絕對時刻概作。比如,你用dispatch_walltime
來創(chuàng)建2016年11月11號11點11分11秒時刻悯许∑袜拢可以用dispatch_walltime
來實現(xiàn)鬧鐘,不過此方法精度不高先壕。dispatch_walltime
根據(jù)結(jié)構體timespec來創(chuàng)建時刻的瘩扼,如下面例子:
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 用來對block分組谆甜。當你有個任務,并且些任務要求其它任務都完成時才能開始集绰,這時你就可以和Dispatch Group來實現(xiàn)规辱。當然簡單的情況下,你可以把任務都放進一個線性queue中栽燕,在queue尾放入那個任務罕袋。但是如果遇到并發(fā)隊列或有多個隊列情況時,就變得很復雜碍岔,dispatch group應此而生浴讯。下因代碼例子將三個block分到一個組,并在這些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é)果將是(前三個輸出順序不定):
blk1
blk2
blk0
done
首先我們用dispatch_group_create
創(chuàng)建dispatch_group_t實例榆纽。因為函數(shù)中有"create",當不再需要它時你必須release它, GCD中所有的release都用dispatch_release
捏肢。函數(shù)dispatch_group_async
就像dispatch_async
把block添加到隊列中奈籽,唯一的區(qū)別是多了一個參數(shù)dispatch_group_t,將block和dispatch group關聯(lián)起來鸵赫。當block和某個dispatch group關聯(lián)之后衣屏,block會擁有這個dispatch group,(dispatch_retain辩棒,就如同block被添加到queue中時會retain這個queue一樣)狼忱,當block執(zhí)行完時,block會釋放它擁有的dispatch group(dispatch_release). 當所有與dispatch group相關聯(lián)的block執(zhí)行完時盗温,dispatch_group_notify
會將block("done")添加到main queue上藕赞。dispatch_group_notify
并不會阻塞當前線程,它監(jiān)聽指定的dispach group卖局,當該group上所有block都執(zhí)行時斧蜕,它將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
的第二個參數(shù)是timeout(dispatch_time_t)指定等待的時長, 如果在timeout時長內(nèi)group上的任務都完成則返回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相關聯(lián)的任務都已完成
} else {
//與group相關聯(lián)的任務中還有在運行
}
當dispatch_group_wait
被調(diào)用時染坯,并不會從該函數(shù)立即返回均芽,當前線程阻塞在此函數(shù)上,等待group上所有任務完成单鹿,或者timeout掀宋。
你可以用DISPATCH_TIME_NOW
來查看group上的任務是否都已完成:
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW)
if (result == 0) {
//此時group上任務都已完成
} else {
//此時group上還有任務在運行
}
在關聯(lián)block和group時,dispatch_group_async
并不是唯一的函數(shù),還有dispatch_group_enter
(與之成對的是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)測group1湃鹊。當blk0,blk1,blk3都完成時,添加block到queue
*/
dispatch_group_notify(group1, queue, ^{
NSLog("blk0, blk1, blk3, all have finished!");
});
/*
* 監(jiān)測group2镣奋, 當blk1, blk2, blk3 都完成時添加block到mainQueue
*/
dispatch_group_notify(group2, mainQueue, ^{
NSLog("blk1, blk2, blk3, all have finished!");
});
注意dispatch_group_enter
和dispatch_group_leave
必須保持平衡(成對出現(xiàn))币呵。如果你需要把一個block關聯(lián)到不同的group上,你只能使用dispatch_group_enter
函數(shù)對侨颈,否則使用dispatch_group_async
更方便一點余赢。
下面看代碼是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ù)只能兩個參數(shù),一個是queue哈垢,一個block妻柒。作用是把block加到queue上,特殊的地方在于温赔,被加queue上的block稱為barrier block蛤奢。barrier block 把queue上的任務的執(zhí)行分成三個階段:
- 將先于barrier block前提交到queue上的所有block執(zhí)行完
- 執(zhí)行barrier block
- 只有當barrier block執(zhí)行好后,才會執(zhí)行后于barrier block添加到queue上的block
這里的參數(shù)queue陶贼,應該是你通過dispatch_queue_create
創(chuàng)建的并發(fā)queue,如果你傳入的是線性queue或者全局并發(fā)queue待秃,函數(shù)作用就和dispatch_queue_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_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);
我們要實現(xiàn)這樣的功能章郁,在洗澡的時候同時洗臉刷牙洗臉枉氮。然后做完上述這些事之后(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);
對數(shù)據(jù)庫或文件進行寫操作時惹悄,我們也可以用此函數(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ù)字的排列順序是不定的。
再看一個例子:
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ù)等待所有任務的完成
*/
dispatch_apply([array count], queue, ^(size_t index) {
/*
* 異步地對array上的對象進行操作 */
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
/*
* dispatch_apply 所有的任務已完成*/
/*
* 在 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);
用來確保在整個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;』。此時線程A的執(zhí)行權被剝奪,線程B獲得執(zhí)行權箫柳,執(zhí)行這塊代碼時手形,initailized仍舊為NO,所以又進行了一次初始化滞时。所以當多線程情況下叁幢,第一種方法可能會被執(zhí)行多次。而dispatch_once
就沒問題坪稽,它是線程安全的曼玩。dispatch_once
經(jīng)常被用于創(chuàng)建單例。
dispatch_suspend/dispatch_resume
這兩個方法用來suspend或者resume dispatch queue的執(zhí)行窒百。一個dispatch queue可以這樣被suspend(休眠):
dispatch_suspend(queue);
然后resume:
dispatch_resume(queue);
suspend并不會影響那些已經(jīng)在執(zhí)行的任務黍判。它只會阻止還在queue的task的執(zhí)行。resume之后篙梢,queue上的任務又可以繼續(xù)被調(diào)度執(zhí)行顷帖。
你可以多次調(diào)用dispatch_suspend(queue);
每調(diào)用一次queue的計數(shù)加一,中要此計數(shù)大于0渤滞,queue就一直處于休眠中贬墩。所以請保持dispatch_suspend(queue);
和dispatch_resume(queue);
的平衡。
Dispatch Semaphore
Dispatch semaphore 相比傳統(tǒng)的信號量機制性能上更優(yōu)秀妄呕;只有當線程需要阻塞時陶舞,dispatch semaphore才會調(diào)用系統(tǒng)內(nèi)核;如果不會阻塞線程绪励,則不會進行系統(tǒng)內(nèi)核的調(diào)用肿孵。Dispatch semaphore也是通過計數(shù)來實現(xiàn)多線程中的控制的。當counter大于0時疏魏,程序繼續(xù)執(zhí)行停做;當counter小于等于0,程序就等待在那里直到counter大于0大莫,才繼續(xù)執(zhí)行蛉腌。
相關函數(shù)就三個:
/創(chuàng)建信號量變量
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.
//把信號量計數(shù)加1,如果之前信號量值小于等于0則喚醒一個由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.
//把信號量計數(shù)減1葵硕,如果減1后值小于0眉抬,此函數(shù)以先進先出FIFO的順序阻塞等待singal。
//如果成功則返回0懈凹; 否則返回非0蜀变,表示timeout(timeout的時間過去了,還是沒收到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.
*/
}
應用場景大致可歸為么兩類:
多個線程訪問有限資源
兩個線程之間相對某事件的同步
比如有兩個線程A和B介评,B的某一任務mession1執(zhí)行的前提條件是A完成A的任務mession2库北。當執(zhí)行到B的mession1時爬舰,如果A的mession2沒有完成,B就被阻塞在那里直到A完成mession1.
多個線程訪問有限資源
先看代碼:
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寒瓦,即多個線程同時修改array情屹,這就會使array變臟(corrupted)數(shù)據(jù)一至性問題,導至程序crash杂腰。我們可以用dispatch semaphore來防止多個線程同時修改array垃你。
使用dispatch semaphore后,上面代碼應是這樣的:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//你的資源數(shù)只有一個(即array)喂很,所以參數(shù)傳1惜颇;參數(shù)值應該等于你的資源個數(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]];
//完成任務少辣,釋放資源的控制凌摄,使別的線程也能訪問。
dispatch_semaphore_signal(semaphore);
});
}
dispatch_release(semaphore);
兩個線程之間相對某事件的同步
看代碼
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"應該在"Mum give a life to me"之后發(fā)生锨亏。但是上述代碼這兩個事件的執(zhí)行順序是不確定的。
使用semaphore代碼應該是這樣:
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);