這是GCD介紹的第三篇文章。
如果說(shuō)串行隊(duì)列可以很好的替代互斥鎖症昏,那么并發(fā)隊(duì)列就可以很好的替代多線(xiàn)程。
并發(fā)隊(duì)列允許你入隊(duì)多個(gè)block议经,并且它們的執(zhí)行不需要等待前一個(gè)block執(zhí)行結(jié)束才開(kāi)始横堡。
運(yùn)行下面這段程序幾次:
#import <Foundation/Foundation.h>
void print(int number) {
for (int count = 0; count < 10; ++count) {
NSLog(@"%d", number);
}
}
int main(int argc, const char * argv[]) {
dispatch_queue_t queue = dispatch_queue_create("My concurrent queue", DISPATCH_QUEUE_CONCURRENT);
@autoreleasepool {
for (int index = 0; index < 5; ++index) {
dispatch_async(queue, ^{
print(index);
});
}
}
dispatch_main();
return 0;
}
dispatch_async()
告訴GCD去入隊(duì)一個(gè)block埋市,但是不用等待這個(gè)block執(zhí)行完畢才繼續(xù)接下來(lái)的操作。這就允許我們可以快速一次把5個(gè)block入隊(duì)到我們剛才創(chuàng)建的隊(duì)列中命贴。
當(dāng)?shù)谝粋€(gè)block被入隊(duì)時(shí)道宅,隊(duì)列還是空的,和串行隊(duì)列一樣這個(gè)block會(huì)立即開(kāi)始執(zhí)行胸蛛。然而污茵,第二個(gè)block入隊(duì)之后也會(huì)立即執(zhí)行,即使第一個(gè)block還沒(méi)執(zhí)行完畢胚泌,第三個(gè)省咨,第四個(gè),第五個(gè)block也是如此玷室,它們會(huì)一起開(kāi)始執(zhí)行零蓉。
每一個(gè)入隊(duì)的blcok在創(chuàng)建的時(shí)候獲取了一個(gè)
index
值,然后將這個(gè)值打印10次穷缤。程序的輸出結(jié)果如你所料嗎敌蜂?為什么每次運(yùn)行輸出的結(jié)果都不一樣?
<p>
如果我們把并發(fā)隊(duì)列換成串行隊(duì)列結(jié)果會(huì)怎么樣呢津肛?試試吧章喉!把
DISPATCH_QUEUE_CONCURRENT
換成DISPATCH_QUEUE_SERIAL
,然后再運(yùn)行看看身坐。
用隊(duì)列秸脱,不用線(xiàn)程
你可能沒(méi)注意到,上面的程序在完全不用pthread_create()
或NSThread
的情況下部蛇,輕松得創(chuàng)建的5個(gè)線(xiàn)程摊唇。因?yàn)椴l(fā)隊(duì)列中的每一個(gè)block需要同時(shí)運(yùn)行,所以GCD會(huì)為每一個(gè)block自動(dòng)的創(chuàng)建(或搶占)一個(gè)線(xiàn)程來(lái)執(zhí)行它們涯鲁,一旦有某一個(gè)block執(zhí)行完畢巷查,它對(duì)應(yīng)的線(xiàn)程就會(huì)被銷(xiāo)毀或者放回線(xiàn)程池中。使用GCD抹腿,你只需要關(guān)心隊(duì)列岛请,讓GCD庫(kù)去關(guān)心線(xiàn)程的事。
雖然你不用手動(dòng)去管理線(xiàn)程警绩,但是你還是不能忽略線(xiàn)程的限制崇败。如果你的入隊(duì)的block數(shù)量超過(guò)了進(jìn)程中的可用線(xiàn)程數(shù),你的程序?qū)?huì)終止運(yùn)行肩祥,而這通常是沒(méi)有預(yù)警的僚匆。
障礙(Barriers)
這時(shí)自然會(huì)有一個(gè)問(wèn)題產(chǎn)生:既然并發(fā)隊(duì)列允許所有的block一起執(zhí)行微渠,那為什么它還叫“隊(duì)列”呢?它不是更像一個(gè)可以加入并行執(zhí)行block的堆嗎咧擂?
當(dāng)你考慮到障礙(Barriers)的時(shí)候,并發(fā)隊(duì)列看起來(lái)就像一個(gè)”隊(duì)列“了大咱。加入障礙(Barriers)之后歌溉,你通過(guò)dispatch_barrier_sync()
和dispatch_barrier_async()
入隊(duì)的block會(huì)發(fā)生一些有趣的事:這個(gè)障礙block(譯者注:障礙也是以block的形式加入隊(duì)列中的)將會(huì)被入隊(duì)凿叠,但是不會(huì)立即執(zhí)行,而是會(huì)等到在它之前入隊(duì)的隊(duì)列執(zhí)行完畢之后才會(huì)開(kāi)始執(zhí)行贸桶。另外,所有在障礙block之后入隊(duì)的block會(huì)等到障礙block本身執(zhí)行完畢之后才會(huì)執(zhí)行桌肴』噬福可以把障礙block看做是一個(gè)“瓶頸”,在一系列并行執(zhí)行的操作中坠七,強(qiáng)制串行執(zhí)行水醋。
下面這段程序展示了的屏障(Barriers)的作用:
#import <Foundation/Foundation.h>
void print(int number) {
for (int count = 0; count < 10; ++count) {
NSLog(@"%d", number);
}
}
int main(int argc, const char * argv[]) {
dispatch_queue_t queue = dispatch_queue_create("My concurrent queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_suspend(queue); // Suspend the queue so blocks are enqueued, but not executed
@autoreleasepool {
// Enqueue five blocks
for (int index = 0; index < 5; ++index) {
dispatch_async(queue, ^{
print(index);
});
}
// Enqueue a barrier
dispatch_barrier_async(queue, ^{
NSLog(@"--- This is a barrier ---");
});
// Enqueue five more blocks
for (int index = 5; index < 10; ++index) {
dispatch_async(queue, ^{
print(index);
});
}
}
dispatch_resume(queue); // Go!
dispatch_main();
return 0;
}
運(yùn)行一下這段程序,注意到在障礙之前彪置,只有index為0到4的block執(zhí)行了拄踪,而在障礙之后,只有index為5到9的block執(zhí)行了拳魁。但是在障礙前后的單獨(dú)一邊惶桐,5個(gè)block是同時(shí)執(zhí)行的。
讀寫(xiě)鎖(Readers-Writer Locks)
在我之前的文章中潘懊,我介紹了如何使用串行隊(duì)列保護(hù)數(shù)據(jù)變量以防止競(jìng)態(tài)條件的發(fā)生姚糊。這種方法使得同一時(shí)間只有一個(gè)線(xiàn)程訪(fǎng)問(wèn)變量,保證了操作的原子性授舟。
但是實(shí)際上救恨,在同步執(zhí)行的操作中我們不用做任何事來(lái)防止靜競(jìng)態(tài)條件的發(fā)生,我們需要做的是防止數(shù)據(jù)被異步的改變岂却,而多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)而不改變同一份數(shù)據(jù)是被允許的(從性能的角度講忿薇,這也應(yīng)該是較好的做法)。
這時(shí)我們就需要一個(gè)讀寫(xiě)鎖(readers-writer lock)躏哩,它的功能是允許對(duì)數(shù)據(jù)讀的操作可以是并行同時(shí)發(fā)生的署浩,但是寫(xiě)的操作必須是串行的。
使用異步隊(duì)列和障礙扫尺,我們可以輕松的實(shí)現(xiàn)一個(gè)讀寫(xiě)鎖筋栋,這里有一個(gè)例子:
#import <Foundation/Foundation.h>
dispatch_queue_t queue;
NSString *he = @"Luke";
NSString *she = @"Megan";
void printAndRepeat() {
NSLog(@"%@ likes %@!", he, she);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue,
// This block is dispatch_async'd to the concurrent queue after 1 second
^{
printAndRepeat();
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
queue = dispatch_queue_create("Reader-writer queue", DISPATCH_QUEUE_CONCURRENT);
// Create readers
for (int index = 0; index < 5; ++index) {
dispatch_async(queue, ^{
printAndRepeat();
});
}
// Change the variables after 5 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(),
// This block is enqueued onto the main queue after 5 seconds.
^{
dispatch_barrier_async(queue, ^{
he = @"Don";
she = @"Alice";
});
});
}
dispatch_main();
return 0;
}
你目前可以先忽略
dispatch_after()
的調(diào)用,它只是簡(jiǎn)單的告訴GCD在一段時(shí)間后入隊(duì)一個(gè)block正驻。
在這個(gè)例子中弊攘,障礙block(把he
和she
改成“Don”和“Alice”)保證了對(duì)數(shù)據(jù)修改這一操作的原子性抢腐。因?yàn)檎系K隊(duì)列運(yùn)行時(shí)不會(huì)被其他block中斷或打擾,所以你永遠(yuǎn)不會(huì)看見(jiàn)“Luke likes Alice!”打印在你的控制臺(tái)襟交。
恭喜迈倍!你現(xiàn)在對(duì)并發(fā)隊(duì)列有了一定的了解,知道了如何用它取代多線(xiàn)程捣域,以及如何利用它創(chuàng)建一個(gè)高效的讀寫(xiě)鎖啼染。在下一篇文章中,我們將研究一下全局并發(fā)隊(duì)列和目標(biāo)隊(duì)列焕梅,下次見(jiàn)迹鹅!