目錄
GCD
在開始討論queue和thread之前, 我們有必要先看看繞不開的GCD -- 一套被"神話"的并發(fā)APIs
如果想惡補(bǔ)一下基礎(chǔ)知識, 請參考Objective-C學(xué)習(xí) 之 GCD
dispatch_sync
Apple Developer Documentation對dispatch_sync最權(quán)威的解釋如下
Submits a block object for execution on a dispatch queue and waits until that block completes
dispatch_sync有兩個很重要的特點(diǎn)
1: this function does not return until the block has finished
這句話似乎是老生常談了, 那為什么還要接著談呢? 這是因?yàn)樗鼤餯eadlock
Calling this function and targeting the current queue results in deadlock
我們來通過一個"精美的插圖"(在哪里? 我要看!)來理解發(fā)生deadlock時的情形

前面的等后面的, 后面的等前面的, 整個queue就這樣"癱瘓"了
上面舉的是main thread和main queue的例子, 其實(shí)
只要是運(yùn)行dispatch_sync的queue和運(yùn)行dispatch_sync block的是同一個queue都會出現(xiàn)deadlock
明白了原理, 我們來看看人家FMDB是怎么做的以屏蔽deadlock
// 第一個部分
/*
* A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses.
* This in turn is used for deadlock detection by seeing if inDatabase: is called on
* the queue's dispatch queue, which should not happen and causes a deadlock.
*/
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
// 第二部分
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
// 第三部分
- (void)inDatabase:(void (^)(FMDatabase *db))block {
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
FMDBRetain(self);
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
FMDBRelease(self);
}
2: As an optimization, this function invokes the block on the current thread when possible
可能很多開發(fā)者都忽略了這點(diǎn), so請反復(fù)讀3遍以加深印象(至少我之前沒有理解這個特點(diǎn)時就曾常常困惑)
為了證實(shí), 我們來看下實(shí)際運(yùn)行的結(jié)果如何
實(shí)例1:
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
dispatch_sync(myQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
輸出如下:
// 注意這里的thread number = 1, 表示和buttonOnClicked都處于同樣的main thread
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 0
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 1
...
currentThread = <NSThread: 0x7fae2a607f20>{number = 1, name = main}, i = 99
dispatch_async
如果你確定"真正"了解了dispatch_sync, 那么理解dispatch_async起來就"簡直"了
按照國際慣例, 我們先來看看Apple Developer Documentation對dispatch_async的權(quán)威解釋
Submits a block for asynchronous execution on a dispatch queue and returns immediately
是不是很簡單? 那我們就直接看例子吧
實(shí)例2:
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
dispatch_async(myQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 2
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
輸出如下:
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 0
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 1
...
currentThread = <NSThread: 0x7fae2a510fa0>{number = 2, name = (null)}, i = 99
?queue
queue又稱dispatch queue, 作為一個"搬運(yùn)工", 它在Apple Developer Documentation中的定義如下
A dispatch queue is an object-like structure that manages the tasks you submit to it
注意這里的關(guān)鍵詞: structure
沒錯, queue只是一個數(shù)據(jù)結(jié)構(gòu), 并且是這樣一個FIFO的數(shù)據(jù)結(jié)構(gòu)
All dispatch queues are first-in, first-out data structures
對于上面queue的定義, 可能還有另外一個會讓你困惑的地方, 那就是task, 那什么是task呢?
A task is simply some work that your application needs to perform
serial queue和concurrent queue
確定, 一定以及肯定理解以上概念后, 我們來看下實(shí)際開發(fā)中有哪些不同類型的queue
serial: Serial queues execute one task at a time in the order in which they are added to the queue
Concurrent: Concurrent queues execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue
概念就這么念完了, 到底理解多少個體差異應(yīng)該是很大的, 下面我就來"嘮叨"幾句
queue中的每個task在哪個線程執(zhí)行, 是無法確定的(好吧, 我承認(rèn)其實(shí)并不嚴(yán)謹(jǐn), 但在沒有深入討論之前, 這么說是最簡單明了不過了)
因?yàn)閝ueue中task的執(zhí)行和調(diào)度是GCD封裝好的, 開發(fā)者不需要care, 這也是GCD最大的價值
不管是serial queue還是conconcurrent queue, 所有的task都是FIFO的(不然怎么說是queue嘛)
但是serial queue中的task是等上一個執(zhí)行完了才執(zhí)行下一個, 而concurrent queue中的task雖然也是FIFO的, 但不等上一個task執(zhí)行下, 下一個task或許就執(zhí)行了
所以實(shí)際運(yùn)行的結(jié)果是, serial queue里的task都是在同一個thread執(zhí)行的, 而concurrent queue的task可能在多個不同的thread中執(zhí)行
注意這里的concurrent queue task用的是"可能"這個詞, 為什么? concurrent難道不就是指多線程么?
是的, 并發(fā)通常是指多線程, 但是你也知道, dispatch queue這一套機(jī)制是GCD封裝調(diào)度的
而concurrent queue里的task是否需要在多個線程中并發(fā)執(zhí)行, GCD會根據(jù)實(shí)際task的情況來決定
為什么? 因?yàn)槎嗑€程并不意味著就高效率, 線程的開銷和調(diào)度也是需要成本的, 這種task和線程成本的權(quán)衡, GCD幫我們做了(這也是我們?yōu)槭裁匆褂肎CD, 而不是自己去實(shí)現(xiàn)一套, GCD考慮得比開發(fā)者考慮的更完善)
main queue
main queue不需要自己創(chuàng)建, 直接使用如下接口即可獲取
dispatch_queue_t dispatch_get_main_queue(void);
Apple Developer Documentation對main queue的準(zhǔn)確定義如下
The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread
它有幾個很重要的特點(diǎn):
它是由系統(tǒng)創(chuàng)建并全局可見的
它是一個serial queue
它的task都是在main thread運(yùn)行的
特點(diǎn)很簡單, 但是使用main queue時, 有一點(diǎn)是需要注意的, 那就是deadlock, 所以main queue通常和diapatch_async一起使用
dispatch_async(dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
global queue
除了main queue, 系統(tǒng)還為我們創(chuàng)建了concurrent的global queue, 方便開發(fā)者使用(為什么說GCD是一套"神"接口, 真是想開發(fā)者所想啊)
global queue通過以下接口獲取
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
系統(tǒng)默認(rèn)已經(jīng)我們創(chuàng)建好了4種不同優(yōu)先級的global queue, 通過參數(shù)long identifier即可獲取相對應(yīng)的queue
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
自己創(chuàng)建queue
上面討論的serial類型的main queue和concurrent類型的global queue都是系統(tǒng)為我們創(chuàng)建好的
如果想要創(chuàng)建自己的queue怎么辦呢? 答案是這個接口
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
重點(diǎn)需要關(guān)注這里的參數(shù)dispatch_queue_attr_t attr
想要創(chuàng)建serial queue, 把a(bǔ)ttr設(shè)置成DISPATCH_QUEUE_SERIAL或者NULL
想要創(chuàng)建concurrent queue, 把a(bǔ)ttr設(shè)置成DISPATCH_QUEUE_CONCURRENT
測驗(yàn)
概念和道理就這么多了, 不知道"聰明"的你理解了多少呢?(實(shí)不相瞞, 我自己也是花了好多心思才"入了門")
下面進(jìn)入測驗(yàn)環(huán)節(jié)來檢驗(yàn)下自己理解得怎么樣吧
為了測試答案的一致性, 有以下幾點(diǎn)說明
運(yùn)行下面測試代碼的app除了創(chuàng)建工程時的模板代碼外, 只單獨(dú)(注意這里的措辭!)包含下面每個測試題的代碼
下面的buttonOnClicked都是在main thread中執(zhí)行的
測試1
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
請問打印的currentThread的thread number是?
A: 1 (即主線程)
B: 2 (即子線程)
C: 2...N (即多個子線程)
D: 以上答案都是忽悠人的
測試2
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
for (NSInteger i = 0; i < 100; i++) {
// currentThread number = 1
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
請問打印的currentThread的thread number是?
A: 1 (即主線程)
B: 2 (即子線程)
C: 2...N (即多個子線程)
D: 以上答案都是忽悠人的
測試3
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
請問打印的currentThread的thread number是?
A: 1 (即主線程)
B: 2 (即子線程)
C: 2...N (即多個子線程)
D: 以上答案都是忽悠人的
測試4
- (IBAction)buttonOnClicked:(UIButton *)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"currentThread = %@, i = %ld", [NSThread currentThread], (long) i);
}
});
}
請問打印的currentThread的thread number是?
A: 1 (即主線程)
B: 2 (即子線程)
C: 2...N (即多個子線程)
D: 以上答案都是忽悠人的
測驗(yàn)就到這里, 祝大家玩的愉快
慢著? 沒有答案? 如果想要正確答案的話, 請聯(lián)系我, 哈哈……
更多文章, 請支持我的個人博客