本篇文章由我們團(tuán)隊(duì)的郭杰童鞋翻譯完成。這是關(guān)于GCD系列的第三篇文章覆积,原文是GCD Concurrent Queues听皿。
如果說串行隊(duì)列是互斥量更好的替代品的話,那么并發(fā)隊(duì)列就是線程的一個(gè)更好的替代品宽档。
并發(fā)隊(duì)列可以讓你入隊(duì)的block且并發(fā)執(zhí)行尉姨,而不需要等待前面入隊(duì)的?block完成運(yùn)行。
多次運(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ì)block塊吗冤,而不是等到block塊移動(dòng)之前完成又厉。這就使我們能夠快速的將5個(gè)block置于我們剛剛創(chuàng)建的并發(fā)隊(duì)列中九府。
當(dāng)?shù)谝粋€(gè)block塊入隊(duì)時(shí),隊(duì)列是空的覆致,因此如果當(dāng)前隊(duì)列是串行侄旬,它會(huì)按照同樣的方式來運(yùn)行。但是煌妈,當(dāng)?shù)诙€(gè)block被入隊(duì)時(shí)勾怒,即使第一個(gè)block還沒有運(yùn)行完成,第二個(gè)也依然會(huì)運(yùn)行声旺。當(dāng)然了,這種方式也同樣適用于第三個(gè)段只、第四和第五個(gè)block腮猖,它們都會(huì)在同一時(shí)間開始運(yùn)行。
隊(duì)列上的每一個(gè)block在創(chuàng)建時(shí)會(huì)捕獲index索引值赞枕, 并會(huì)打印10次記錄澈缺。這個(gè)程序的輸出符合您的預(yù)期嗎?為什么每次運(yùn)行程序的輸出結(jié)果都是不一樣的呢?
<p>
如果我們使用串行隊(duì)列的話炕婶,程序的輸出會(huì)有什么不同呢姐赡?嘗試將
DISPATCH_QUEUE_CONCURRENT
修改為DISPATCH_QUEUE_SERIAL,
然后再次運(yùn)行程序柠掂,試試看项滑。
用隊(duì)列,不用線程
你可能已經(jīng)錯(cuò)過了線程涯贞,但是上面的程序在不使用pthread_create()
或NSThread
的情況下枪狂,毫不費(fèi)力地創(chuàng)建并執(zhí)行五個(gè)線程。由于在并發(fā)隊(duì)列中每一個(gè)block都必須是同時(shí)運(yùn)行的宋渔,GCD會(huì)自動(dòng)創(chuàng)建(或征用)一個(gè)線程來運(yùn)行它們中的每一個(gè)州疾。每個(gè)block一旦完成,該線程會(huì)被摧毀或者返回到一個(gè)線程池中皇拣。使用GCD严蓖,你可以專注于隊(duì)列,并讓有關(guān)線程庫去考慮線程問題氧急。
雖然你不用手動(dòng)管理線程颗胡,但這并不意味著你可以忽視線程的限制。如果入隊(duì)的并發(fā)block比可用的線程更多吩坝,你的程序可能會(huì)出問題杭措。
障礙(Barriers)
在這個(gè)點(diǎn)上的一個(gè)很自然的問題是:如果并發(fā)隊(duì)列允許所有的block執(zhí)行,那么為什么它們被稱為”隊(duì)列“呢钾恢?它不是更像一個(gè)可以加入并發(fā)執(zhí)行block的堆嗎手素?
當(dāng)你考慮障礙時(shí)鸳址,并發(fā)隊(duì)列的行為看起來就像隊(duì)列了。?使用dispatch_barrier_sync()
或者dispatch_barrier_async()
入隊(duì)的block會(huì)帶來一些有意思的事情:這個(gè)block塊將會(huì)被入隊(duì)泉懦,但是會(huì)等到所有的之前入隊(duì)的block執(zhí)行完成后才開始執(zhí)行稿黍。除此之外,在?barrier block后面入隊(duì)的所有的block崩哩,會(huì)等到到barrier block本身已經(jīng)執(zhí)行完成之后才繼續(xù)執(zhí)行巡球。barrier block通常被看作是一系列的并發(fā)操作集合中的"choke points"(咽喉要道)。
為了展示障礙block邓嘹,看看下面的程序:
#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)行這個(gè)程序酣栈。可以注意到barrier之前的那些block汹押,只有索引為0到4的block是被允許執(zhí)行到完成矿筝,而在這個(gè)barrier之后,僅僅序號(hào)為5到9的block會(huì)被執(zhí)行棚贾。然而窖维,在?barrier兩邊,每組5個(gè)block塊被允許在同一個(gè)時(shí)間執(zhí)行妙痹。
讀寫鎖
在我上一篇博客中铸史,我講了如何使用串行隊(duì)列去?保護(hù)一組狀態(tài)變量。使用這個(gè)技術(shù)怯伊,僅有一個(gè)線程可以在一個(gè)時(shí)間內(nèi)訪問一個(gè)變量琳轿,從而保證了原子行為。
但是說實(shí)話耿芹,我們沒有必要在讀取數(shù)據(jù)時(shí)去保護(hù)這些數(shù)據(jù):我們僅僅需要在異步修改時(shí)去保護(hù)它們利赋。允許多個(gè)線程讀取數(shù)據(jù)而不改變數(shù)據(jù)從某些角度(如性能)來說是非常好的。
我們需要的是一個(gè)讀寫鎖猩系,即寫入的時(shí)候串行化訪問操作媚送,但是允許多個(gè)讀操作并發(fā)。
我們可以使用異步隊(duì)列和barrier輕松實(shí)現(xiàn)一個(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;
}
你現(xiàn)在可以忽略調(diào)用
dispatch_after()
塘偎,它只是簡(jiǎn)單的告訴GCD在一段時(shí)間后入隊(duì)一個(gè)block。
在這個(gè)例子當(dāng)中拿霉,barrier block是保證了修改操作的原子性吟秩。因?yàn)閎arrier block總是不間斷的運(yùn)行,你將不會(huì)看到有“Luke like Alice!”打印出來绽淘。
恭喜你涵防!你已經(jīng)了解了關(guān)于并發(fā)隊(duì)列的所有內(nèi)容,以及它們是怎樣被用來代替線程的并創(chuàng)建有效的讀寫鎖沪铭。在下一篇文章中壮池,我們將解析全局并發(fā)隊(duì)列和目標(biāo)隊(duì)列偏瓤。下次見!