iOS开发 之 Queue和Thread

目录

GCD

在开始讨论queue和thread之前, 我们有必要先看看绕不开的GCD -- 一套被"神话"的并发APIs

如果想恶补一下基础知识, 请参考Objective-C学习 之 GCD

dispatch_sync

Apple Developer Documentation对dispatch_sync最权威的解释如下

Submits a block object for execution on a dispatch queue and waits until that block completes

dispatch_sync有两个很重要的特点

1: this function does not return until the block has finished

这句话似乎是老生常谈了, 那为什么还要接着谈呢? 这是因为它会引起deadlock

Calling this function and targeting the current queue results in deadlock

我们来通过一个"精美的插图"(在哪里? 我要看!)来理解发生deadlock时的情形

queue-and-thread-01.png

前面的等后面的, 后面的等前面的, 整个queue就这样"瘫痪"了

上面举的是main thread和main queue的例子, 其实

只要是运行dispatch_sync的queue和运行dispatch_sync block的是同一个queue都会出现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

可能很多开发者都忽略了这点, so请反复读3遍以加深印象(至少我之前没有理解这个特点时就曾常常困惑)

为了证实, 我们来看下实际运行的结果如何

实例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的权威解释

Submits a block for asynchronous execution on a dispatch queue and returns immediately

是不是很简单? 那我们就直接看例子吧

实例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, 作为一个"搬运工", 它在Apple Developer Documentation中的定义如下

A dispatch queue is an object-like structure that manages the tasks you submit to it

注意这里的关键词: structure

没错, queue只是一个数据结构, 并且是这样一个FIFO的数据结构

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

确定, 一定以及肯定理解以上概念后, 我们来看下实际开发中有哪些不同类型的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

概念就这么念完了, 到底理解多少个体差异应该是很大的, 下面我就来"唠叨"几句

queue中的每个task在哪个线程执行, 是无法确定的(好吧, 我承认其实并不严谨, 但在没有深入讨论之前, 这么说是最简单明了不过了)

因为queue中task的执行和调度是GCD封装好的, 开发者不需要care, 这也是GCD最大的价值

不管是serial queue还是conconcurrent queue, 所有的task都是FIFO的(不然怎么说是queue嘛)

但是serial queue中的task是等上一个执行完了才执行下一个, 而concurrent queue中的task虽然也是FIFO的, 但不等上一个task执行下, 下一个task或许就执行了

所以实际运行的结果是, serial queue里的task都是在同一个thread执行的, 而concurrent queue的task可能在多个不同的thread中执行

注意这里的concurrent queue task用的是"可能"这个词, 为什么? concurrent难道不就是指多线程么?

是的, 并发通常是指多线程, 但是你也知道, dispatch queue这一套机制是GCD封装调度的

而concurrent queue里的task是否需要在多个线程中并发执行, GCD会根据实际task的情况来决定

为什么? 因为多线程并不意味着就高效率, 线程的开销和调度也是需要成本的, 这种task和线程成本的权衡, GCD帮我们做了(这也是我们为什么要使用GCD, 而不是自己去实现一套, GCD考虑得比开发者考虑的更完善)

main queue

main queue不需要自己创建, 直接使用如下接口即可获取

dispatch_queue_t dispatch_get_main_queue(void);

Apple Developer Documentation对main queue的准确定义如下

The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread

它有几个很重要的特点:

  • 它是由系统创建并全局可见的

  • 它是一个serial queue

  • 它的task都是在main thread运行的

特点很简单, 但是使用main queue时, 有一点是需要注意的, 那就是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, 系统还为我们创建了concurrent的global queue, 方便开发者使用(为什么说GCD是一套"神"接口, 真是想开发者所想啊)

global queue通过以下接口获取

dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

系统默认已经我们创建好了4种不同优先级的global queue, 通过参数long identifier即可获取相对应的queue

DISPATCH_QUEUE_PRIORITY_HIGH

DISPATCH_QUEUE_PRIORITY_DEFAULT

DISPATCH_QUEUE_PRIORITY_LOW

DISPATCH_QUEUE_PRIORITY_BACKGROUND

自己创建queue

上面讨论的serial类型的main queue和concurrent类型的global queue都是系统为我们创建好的

如果想要创建自己的queue怎么办呢? 答案是这个接口

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

重点需要关注这里的参数dispatch_queue_attr_t attr

想要创建serial queue, 把attr设置成DISPATCH_QUEUE_SERIAL或者NULL

想要创建concurrent queue, 把attr设置成DISPATCH_QUEUE_CONCURRENT

测验

概念和道理就这么多了, 不知道"聪明"的你理解了多少呢?(实不相瞒, 我自己也是花了好多心思才"入了门")

下面进入测验环节来检验下自己理解得怎么样吧

为了测试答案的一致性, 有以下几点说明

  • 运行下面测试代码的app除了创建工程时的模板代码外, 只单独(注意这里的措辞!)包含下面每个测试题的代码

  • 下面的buttonOnClicked都是在main thread中执行的

测试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: 以上答案都是忽悠人的

测验就到这里, 祝大家玩的愉快

慢着? 没有答案? 如果想要正确答案的话, 请联系我, 哈哈……

更多文章, 请支持我的个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • iOS中GCD的使用小结 作者dullgrass 2015.11.20 09:41*字数 4996阅读 20199...
    DanDanC阅读 820评论 0 0
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    dullgrass阅读 37,843评论 30 236
  • 一、前言 本篇博文介绍的是iOS中常用的几个多线程技术: NSThread GCD NSOperation 由于a...
    和珏猫阅读 575评论 0 1
  • 一、前言 上一篇文章iOS多线程浅汇-原理篇中整理了一些有关多线程的基本概念。本篇博文介绍的是iOS中常用的几个多...
    nuclear阅读 2,047评论 6 18
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,018评论 0 4