iOS開發(fā) 之 Queue和Thread

目錄

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-and-thread-01.png

前面的等后面的, 后面的等前面的, 整個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)系我, 哈哈……

更多文章, 請支持我的個人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS中GCD的使用小結(jié) 作者dullgrass 2015.11.20 09:41*字?jǐn)?shù) 4996閱讀 20199...
    DanDanC閱讀 1,288評論 0 0
  • 本篇博客共分以下幾個模塊來介紹GCD的相關(guān)內(nèi)容: 多線程相關(guān)概念 多線程編程技術(shù)的優(yōu)缺點(diǎn)比較? GCD中的三種隊列...
    dullgrass閱讀 38,108評論 28 236
  • 一、前言 本篇博文介紹的是iOS中常用的幾個多線程技術(shù): NSThread GCD NSOperation 由于a...
    和玨貓閱讀 658評論 0 1
  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關(guān)多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,142評論 6 18
  • 本篇博客共分以下幾個模塊來介紹GCD的相關(guān)內(nèi)容: 多線程相關(guān)概念 多線程編程技術(shù)的優(yōu)缺點(diǎn)比較? GCD中的三種隊列...
    有夢想的老伯伯閱讀 1,088評論 0 4

友情鏈接更多精彩內(nèi)容