iOS开发之底层iOS 面试题整理iOS开发之GCD

iOS-底层探索22:GCD上-函数与队列&面试题

2020-11-03  本文已影响0人  differ_iOSER

iOS 底层探索 文章汇总

目录

一、GCD简介

  1. 什么是GCD:
  1. GCD的优势:

二、任务、函数、队列

  1. 任务使用block封装
  2. 任务的block没有参数也没有返回值
  3. 执行任务的函数
    • 异步dispatch async函数
      不用等待当前语句执行完毕,就可以执行下一条语句
      会开启线程执行block的任务
      异步是多线程的代名词
  1. 队列
    队列用来调度任务,任务都做线程中执行
    串行队列和并发队列都符合FIFO原则(先进先出并不是先进先执行完成)
  1. 队列和函数
    异步函数+并发队列 开启多条新的子线程、并发
    异步函数+串行队列 为所有串行队列中的任务仅开启一个新的子线程、串行
    同步函数+并发队列 同步函数中均不开启新的子线程、串行
    同步函数+串行队列 同上...
  1. 主队列和全局队列

主队列

全局队列

三、GCD相关面试题

  1. 下面代码输出什么?
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

这个程序就是典型的死锁,可以看到,只打印了1,就再也没有响应了,已经造成了GCD死锁。为什么会这样呢?让我们来解读一下这段程序的运行顺序:首先会打印1,然后将主队列和一个block传入GCD同步函数dispatch_sync中,等待sync函数执行,直到它返回,才会执行打印3的语句。可是,竟然没有反应了?block中的2没有被打印出来,viewDidLoad()中的3也没有被打印出来。也就是说,block没有得到执行的机会,viewDidLoad也没有继续执行下去。为什么block不执行呢?因为viewDidLoad也是执行在主队列的,它是正在被执行的任务,也就是说,viewDidLoad()是主队列的队头。主队列是串行队列,任务不能并发执行,同时只能有一个任务在执行,也就是队头的任务才能被出列执行。我们现在被执行的任务是viewDidLoad(),然后我们又将block入列到同一个队列,它比viewDidLoad()后入列,遵循先进先出的原理,它必须等到viewDidLoad()执行完,才能被执行。 但是,dispatch_sync函数的特性是,等待block被执行完毕,才会返回,因此,只要block一天不被执行,它就一天不返回。我们知道,内部方法不返回,外部方法是不会执行下一行命令的。不等到sync函数返回,viewDidLoad打死也不会执行print End的语句,因此,viewDidLoad()一直没有执行完毕。block在等待着viewDidLoad()执行完毕,它才能上,sync函数在等待着block执行完毕,它才能返回,viewDidLoad()在等待着sync函数返回,它才能执行完毕。这样的三方循环等待关系,就造成了死锁。

参考:理解GCD死锁
关于GCD的例题:iOS面试题:阿里-P6一面-参考思路

总结:同步串行必阻塞当前队列(不一定会死锁)

  1. 下面代码输出什么?
- (void)viewDidLoad {
    [super viewDidLoad];
 
    dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    //sleep(2);这里会导致打印5会后执行
    NSLog(@"5");
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

答案:15243、15234

大厂面试题:

- (void)viewDidLoad {
    [super viewDidLoad];
 
    dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("differ", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

答案:15243、152死锁(没有NSLog(@"4");也是同样的)

注意: #define DISPATCH_QUEUE_SERIAL NUL

  1. 下面代码有可能输出什么?
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("com.differ.cn", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}

A: 1230789
B: 1237890
C: 3120798
D: 2137890

分析:

![](https://img.haomeiwen.com/i2987980/b6ff3234f735ce80.png?imageMogr2/auto- orient/strip%7CimageView2/2/w/1240)

答案:AC

四、GCD底层分析

1、寻找GCD底层库

添加dispatch_sync符号断点

运行代码:

所以GCD的底层源码在libdispatch.dylib库中

GCD源码初始化方法libdispatch_init(void)

2、队列是如何创建的?串行队列和并发队列创建时如何区分的?

队列创建方法:dispatch_queue_create

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
    return _dispatch_lane_create_with_target(label, attr,
            DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{
    // dqai 创建:根据传入的串行/并发参数创建
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

    // Step 1: Normalize arguments (qos, overcommit, tq)

    ...

    // Step 2: Initialize the queue

    ...
    const void *vtable;
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    // 确定串行队列/并发队列的类名
    if (dqai.dqai_concurrent) {
        // OS_dispatch_queue_concurrent-拼接类名
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }

...

    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc、确定isa指向的类名
    //根据qai.dqai_concurrent区分是串行队列还是并发队列
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init

    dq->dq_label = label;
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
            dqai.dqai_relpri);
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
    if (!dqai.dqai_inactive) {
        _dispatch_queue_priority_inherit_from_target(dq, tq);
        _dispatch_lane_inherit_wlh_from_target(dq, tq);
    }
    _dispatch_retain(tq);
    dq->do_targetq = tq;
    _dispatch_object_debug(dq, "%s", __func__);
    return _dispatch_trace_queue_create(dq)._dq;
}

dqai的创建方法中我们可以发现只要队列创建传入的参数不是DISPATCH_QUEUE_CONCURRENT那就创建并发队列

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
    dispatch_queue_attr_info_t dqai = { };

    if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
    if (dqa == &_dispatch_queue_attr_concurrent) { // null 默认
        dqai.dqai_concurrent = true;
        return dqai;
    }
#endif

....
}
#define DISPATCH_QUEUE_CONCURRENT \
        DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
        _dispatch_queue_attr_concurrent)

通过队列queue的创建过程我们可以看到queue也是一个对象,存在alloc、init、isa

DISPATCH_QUEUE_WIDTH_MAX决定了当前队列能开辟多大:

#define DISPATCH_QUEUE_WIDTH_FULL           0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)

DISPATCH_QUEUE_WIDTH_POOL-全局并发队列宽度:4096-1
DISPATCH_QUEUE_WIDTH_MAX- 一般并发队列宽度: 4096-2

3、异步函数底层分析:dispatch_async

#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    // 任务包装器 - 保存 block
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif

dispatch_async方法中主要调用两个方法:
_dispatch_continuation_init_dispatch_continuation_async
先介绍_dispatch_continuation_init方法_dispatch_continuation_async在下一篇文章分析

DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, dispatch_block_t work,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    void *ctxt = _dispatch_Block_copy(work);

    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        dc->dc_flags = dc_flags;
        dc->dc_ctxt = ctxt;
        // will initialize all fields but requires dc_flags & dc_ctxt to be set
        return _dispatch_continuation_init_slow(dc, dqu, flags);
    }

    dispatch_function_t func = _dispatch_Block_invoke(work);
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;//调用Block的func
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init_f(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    pthread_priority_t pp = 0;
    dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
    dc->dc_func = f;// 保存调用Block的func
    dc->dc_ctxt = ctxt;// 保存Block
    // in this context DISPATCH_BLOCK_HAS_PRIORITY means that the priority
    // should not be propagated, only taken from the handler if it has one
    if (!(flags & DISPATCH_BLOCK_HAS_PRIORITY)) {
        pp = _dispatch_priority_propagate();
    }
    _dispatch_continuation_voucher_set(dc, flags);
    return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}

打印出Block调用的堆栈:

因此的确是_dispatch_call_block_and_release方法调用了Block

上一篇下一篇

猜你喜欢

热点阅读