美文网首页iOS开发之底层iOS 面试题整理iOS开发之GCD
iOS-底层探索22:GCD上-函数与队列&面试题

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

作者: differ_iOSER | 来源:发表于2020-11-03 22:31 被阅读0次

    iOS 底层探索 文章汇总

    目录

    一、GCD简介

    1. 什么是GCD:
    • 全称是Grand Central Dispatch
    • 纯C语言,提供了非常多强大的函数
    • 将任务添加到队列,并且指定执行任务的函数
    1. GCD的优势:
    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    二、任务、函数、队列

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

    主队列

    • 专门用来在主线程上调度任务的串行队列
    • 不会开启线程
    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
    • dispatch get main queue();

    全局队列

    • 为了方便程序员的使用,苹果提供了全局队列dispatch_get_global queue(0, 0)
    • 全局队列是一个并发队列
    • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

    三、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)

    • 打印1、2、3均在同一并发队列中且打印1、2为异步函数,所以1、2、3的打印顺序不是确定的。
    • 重点在于打印3的同步函数,由于其在main串行队列中根据FIFO,会先执行完成打印3然后再执行打印0(同步函数会阻塞当前队列)
    • 执行打印0后再将打印7、8、9任务添加到并发队列中。所以3一定在0之前,7、8、9一定在0之后,1、2位置不确定。

    答案: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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/loebvktx.html