iOS GCD底层分析(1)--函数、队列原理探究

前言

在开发过程中,我们很多时候需要使用GCD来处理业务流程。但是我们还是对队列任务还是一知半解的状态,串行队列并发队列区别,同步函数异步函数,队列和函数的配合使用,GCD下层封装等等,那么我们进行以下的分析。

1.GCD的相关概念

准备工作

libdispatch.dylib

1.1 GCD

GCD全称是Grand Central Dispatch,纯C语⾔,提供了⾮常多强⼤的函数。

GCD的优势所在

  • GCD 是苹果公司为多核并⾏运算提出的解决⽅案
  • GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)
  • GCD⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执⾏什么任务,不需要编写任何线程管理代码

注意:GCD任务添加到队列,并指定执行任务的函数

1.2 任务

任务封装成block,任务的block没有参数也没有返回值。任务通过队列的调度,由线程来执行。

1.3 函数

执行任务的函数分为:异步函数同步函数

  • 异步函数dispatch_async
    • 不⽤等待当前语句执⾏完毕,就可以执⾏下⼀条语句
    • 会开启线程执⾏ block的任务
    • 异步多线程的代名词
  • 同步函数dispatch_sync
    • 必须等待当前语句执⾏完毕,才会执⾏下⼀条语句
    • 不会开启线程
    • 在当前线程执⾏block的任务

1.4 队列

队列分为两种:串行队列并发队列。不同的队列中,任务排列的方式是不一样的,任务通过队列的调度,由线程池安排的线程来执行。
串行队列并发队列都会遵循FIFO的原则,即先进入先调度的原则;任务的执行速度或者说执行时长,与各自任务的复杂度有关

  • 串行队列:通路比较窄,任务按照一定的顺序进行排列,一个一个执行
  • 并发队列:通道比较广,同一时间可能有多个任务执行
    队列执行

1.5 队列与函数

上面理解了队列函数任务的区别,队列用来调用任务,函数用来执行任务。那么队列和函数不同的配合会有怎样的运行效果呢?

  • 同步函数串行队列
    • 不会开启线程,在当前线程中执行任务
    • 任务串行执行,任务一个接着一个执行
    • 会产生阻塞
  • 同步函数并发队列
    • 不会开启线程,在当前线程中执行任务
    • 任务一个接着一个执行
  • 异步函数串行队列
    • 会开启一个线程
    • 任务一个接着一个执行
  • 异步函数并发队列
    • 开启线程,在当前线程执行任务
    • 任务异步执行,没有顺序CPU调度有关

2. GCD相关案例分析

2.1 任务耗时分析

创建一个案例,分析任务的耗时,见下图:

案例分析

1. 两次计时之间,什么也不做,只创建了一个串行队列,耗时0.000001秒,见下图:
案例分析

2.在主线程中执行textMethod,耗时时间有所增加,0.000142秒,见下图:
案例分析

3.将任务放入一个串行队列,并同步执行该任务,耗时0.000117秒。见下图:
案例分析

4.将任务放入串行队列,并异步执行该任务,异步函数会开启一个线程,并执行该任务。耗时0.000007秒
案例分析

5.经过这个案例分析,得出以下结论:

  • 不管是采用什么方式,只要执行任务都会耗时
  • 异步执行是耗时相对较少,异步可以用来解决我们在开发中的并发多线程等问题

2.2 主队列添加同步任务

在当前的main队列中添加一个任务,并同步执行该任务会怎么样呢?见下图:

案例分析

分析:
当前的流程中,默认队列就是主队列,也是一个串行队列,任务执行的顺序是:

  • NSlog(@"0")
  • dispathc_sync任务块
  • NSlog(@"2")
    而此时第二步中的块任务,会向当前的的主队列中添加一个任务NSlog(@"1")。此时就产生相互等待的问题,也就是我们常说的死锁!见下图:
    死锁原因

    当前是一个串行队列dispathc_sync任务块要执行完成需要执行NSlog(@"1"),而NSlog(@"1")需要等到NSlog(@"2")执行完成后才能执行,而NSlog(@"2")又要等dispathc_sync任务块执行完成才会执行。这样主队列就会进入一个相互等待状态,也就是死锁!运行案例,见下图:
    死锁案例分析

    案例果然是出现了崩溃,那么我们要怎么样解决呢?我们把主队列换成自定义的串行队列看看:
    死锁案例分析

    完美解决了死锁的问题!

2.3 并发队列添加异步任务

以下是一个并发队列,通道比较宽,所以这个嵌套过程并不会引起崩溃!见下图:

并发队列案例分析

分析:

  • 因为任务复杂度基本一致,所以打印顺序:1-5-2-4-3
  • dispathc_async开启一个新的线程去执行其中的任务;

运行案例查看结果:

并发队列案例分析

结果跟我们上面分析的一样,如果将异步函数改成同步函数会怎样的?那我们现在就试试看。
分析:
因为并发队列,所以不会导致队列任务的阻塞,同时因为是同步执行,所以不会开启新的线程,按照顺序去执行流程。如下图:
并发队列案例分析

2.4 串行队列添加同步任务

创建一个串行队列,并向该串行队列中添加同步任务3,见下图:

串行队列案例分析

分析:
该案例和主队列添加同步任务是一样的,主队列也是串行队列,所以我的猜想也是会崩溃
案例崩溃分析

任务2开始进入一个串行队列dispathc_sync任务块要执行完成需要执行NSlog(@"3"),而NSlog(@"3")需要等到NSlog(@"4")执行完成后才能执行,而NSlog(@"4")又要等dispathc_sync任务块执行完成才会执行。这样串行队列就会进入一个相互等待状态,也就是死锁!虽然此时是在子线程中,但是队列通道遵循FIFO原则,并且必须等待当前语句执⾏完毕,才会执⾏下⼀条语句。所以会崩溃

运行案例查看结果:

运行结果

果然结果是会崩溃的!

2.5 并发队列多任务

并发队列中,添加同步任务和异步任务,如下图:

并发队列案例分析

分析:

  • 主队列中有10个任务
  • 其中任务3任务0同步任务,所以任务3一定在任务0前面
  • 任务1任务2任务3的顺序是不确定
  • 任务7任务8任务9一定在任务0的后面

3. 队列源码探索

查看汇编断点,发现队列的开辟实在dispatch_queue_create方法实现的,而代码实现实在libdispatch.dyld库实现的,见下图:

打开汇编断点

结论:GCD来自libdispatch.dylib动态库。

3.1 主队列

3.1.1 dispatch_get_main_queue()定义

dispatch_get_main_queue ()分析
进入主队列定义的地方,如下图:

源码定义

从上面的注释中我们可以发现一些内容:

  • 主队列在应用程序中,用于主程序main Runloop
  • 主队列的创建在main函数之前即完成,也即是dyld应用程序加载阶段即已创建
    主队列是通过DISPATCH_GLOBAL_OBJECT获取。全局搜索DISPATCH_GLOBAL_OBJECT,该函数是通过宏进行定义的,定义如下:
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
  • 第一个参数:表示队列类型
  • 第二个参数:主队列的对象,可以理解为通过类型主队列对象封装后得到main队列

全局搜索_dispatch_main_q =,找到了主队列初始化定义的地方,见下代码:

//默认是主串行队列,以上main_queue已经说明
struct dispatch_queue_static_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
    .do_targetq = _dispatch_get_default_queue(true),
#endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
            DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",    //默认的线程label
    //线程宽度为1,默认为1,用来区分串行队列和并行队列,并不是dq_serialnum
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

注意:线程宽度默认为1,用来区分串行队列和并行队列,并不是dq_serialnum字段来区分

主队列是一个结构体,并且继承自DISPATCH_GLOBAL_OBJECT_HEADER,全局搜索DISPATCH_GLOBAL_OBJECT_HEADER,其定义如下:

#if OS_OBJECT_HAVE_OBJC1
#define DISPATCH_GLOBAL_OBJECT_HEADER(name) \
    .do_vtable = DISPATCH_VTABLE(name), \
    ._objc_isa = DISPATCH_OBJC_CLASS(name), \
    .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT, \
    .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT
#else
#define DISPATCH_GLOBAL_OBJECT_HEADER(name) \
    .do_vtable = DISPATCH_VTABLE(name), \
    .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT, \
    .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT
#endif
dispatch_queue_static_s结构体的定义
typedef struct dispatch_lane_s {
    DISPATCH_LANE_CLASS_HEADER(lane);
    /* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN *dispatch_lane_t;

// Cache aligned type for static queues (main queue, manager)
struct dispatch_queue_static_s {
    struct dispatch_lane_s _as_dl[0]; \
    DISPATCH_LANE_CLASS_HEADER(lane);
} DISPATCH_CACHELINE_ALIGN;
3.1.2通过lable查找主队列定义

下面通过label来定位主队列。在主线程中进行debug调试时,运行堆栈信息中,能看到下图的内容:

debug调试定位主队列

图中可以看到线程以及线程执行的任务栈信息,在主线程中,可以看到队列是一个serial类型,并且其对应的lablecom.apple.main-thread

通过上面线索,分别打印自定义串行队列并发队列主队列全局队列。见下图:

自定义队列

在创建自定义队列时,需要传入队列的名称,也就是队列lable,以及队列的类型。通过上面的打印信息,队列都有其对应的名称。根据队列的label,在libdispatch.dylib源码中进行全局搜索。如下:
查找主队列的label

最终定位到的位置和dispatch_get_main_queue()分析时结果是一样的,主队列是一个结构体,并且com.apple.main-thread是默认label

3.1.3 主队列的初始化

前面查看主队列定义的注释时,已经说明在main函数之前进行了初始化,我们在研究dyld时,分析objc_init()初始化时得出过这样的结论:libSystem_init -> libdispatch_init -> objc_init。那么主队列的初始化是否在dispatch_init()方法中呢?

dispatch_init()方法

dispatch_init()中成功找到了主队列初始化的地方,获取默认队列,并将主队列地址绑定到当前队列和主线程中

总结:
主队列在main函数之前,应用程序加载调用dispatch_init()方法时,即完成创建。主队列为当前的默认队列,并绑定到主线程中。

3.2 全局队列

3.2.1全局队列的定义

dispatch_get_global_queue(0, 0),进入全局队列定义的地方,如下:

全局队列的定义

创建全局并发队列时可以传参数,根据不同服务质量或者优先等级提供不同的并发队列。那么我们是不是可以想到:应该有一个全局的集合,去维护这些并发队列。

3.2.1 查找维护的并发队列

全对队列labellable-com.apple.root.default)搜索,得到如下:

全局并发队列集合

总结:
系统会维护一个全局队列集合,根据不同的服务质量或者优先等级提供不同的全局队列。我们在开发工作中默认使用:dispatch_get_global_queue(0, 0)

3.3自定义队列的创建

由于libdispach.dylib的源码不能编译,只能通过关键步骤逐步去定位分析。在源码中全局搜索dispatch_queue_create,结果很多,因为创建时第一个参数是一个常量,优化搜索条件,全局搜索dispatch_queue_create(const,找到创建队列的入口,如下:

//队列入口方法
//label--name  
//attr--队列类型
//DISPATCH_TARGET_QUEUE_DEFAULT=NULL 默认创建串行队列
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);
}
  • lable为队列名称
  • attr当前传入的,要创建的队列类型
  • 调用_dispatch_lane_create_with_target方法,并传入默认队列类型
    DISPATCH_TARGET_QUEUE_DEFAULT定义

    通过上图可以明确,默认队列被定义为NULL
定位_dispatch_lane_create_with_target方法

全局搜索_dispatch_lane_create_with_target(const,定位到队列创建的地方,见下面源码:

//  lable   -> 名称
//  dqa     -> 要创建的类型
//  tq      -> 默认类型 - 串行NULL
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 创建 - dqa传入的属性串行还是并行
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

    //
    // Step 1: Normalize arguments (qos, overcommit, tq)
    //
    dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
    if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
        dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
    }
    if (qos == DISPATCH_QOS_MAINTENANCE) {
        dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
    }
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

    _dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
    if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
        if (tq->do_targetq) {
            DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
                    "a non-global target queue");
        }
    }

    if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
        // Handle discrepancies between attr and target queue, attributes win
        if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
            if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
                overcommit = _dispatch_queue_attr_overcommit_enabled;
            } else {
                overcommit = _dispatch_queue_attr_overcommit_disabled;
            }
        }
        if (qos == DISPATCH_QOS_UNSPECIFIED) {
            qos = _dispatch_priority_qos(tq->dq_priority);
        }
        tq = NULL;
    } else if (tq && !tq->do_targetq) {
        // target is a pthread or runloop root queue, setting QoS or overcommit
        // is disallowed
        if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
            DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
                    "and use this kind of target queue");
        }
    } else {
        if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
            // Serial queues default to overcommit!
            overcommit = dqai.dqai_concurrent ?
                    _dispatch_queue_attr_overcommit_disabled :
                    _dispatch_queue_attr_overcommit_enabled;
        }
    }
    if (!tq) {
        tq = _dispatch_get_root_queue(
                qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
                overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
        if (unlikely(!tq)) {
            DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
        }
    }

    //
    // Step 2: Initialize the queue
    //
    if (legacy) {
        // if any of these attributes is specified, use non legacy classes
        if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
            legacy = false;
        }
    }

    // 类的类型
    const void *vtable; // - 设置类 类是通过宏定义拼接而成
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    if (dqai.dqai_concurrent) {
        // OS_dispatch_##name##_class
        // OS_dispatch_queue_concurrent - 宏定义拼接类类型
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }
    switch (dqai.dqai_autorelease_frequency) {
    case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
        dqf |= DQF_AUTORELEASE_NEVER;
        break;
    case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
        dqf |= DQF_AUTORELEASE_ALWAYS;
        break;
    }
    if (label) {
        const char *tmp = _dispatch_strdup_if_mutable(label);
        if (tmp != label) {
            dqf |= DQF_LABEL_NEEDS_FREE;
            label = tmp;
        }
    }
    // dq创建的地方 - alloc init   队列也是个对象
    //  OS_dispatch_queue_serial
    //  OS_dispatch_queue_concurrent
    //  即然是对象应该有ISA的走向
    dispatch_lane_t dq = _dispatch_object_alloc(vtable, // 类
            sizeof(struct dispatch_lane_s)); // alloc
    _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; // 通过这层可以理解为经过一层封装后,返回的依然是dq
}

分析:
该方法的返回值为dq,虽然调用了_dispatch_trace_queue_create方法对dq进行了封装处理,但是最终返回的依然是dq,所以dq才是我们的研究重点!,以下是提取出dq的创建流程代码:

        // dq创建方 - alloc init   队列也是个对象
        //  OS_dispatch_queue_serial
        //  OS_dispatch_queue_concurrent
        //  即然是对象应该有ISA的走向
        dispatch_lane_t dq = _dispatch_object_alloc(vtable, // 类
                        sizeof(struct dispatch_lane_s)); // alloc
        _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__);
进入_dispatch_object_alloc方法
//类似于对象的初始化,vtable就是要初始化的对象,size就是申请内存的大小
void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
    const struct dispatch_object_vtable_s *_vtable = vtable;
    dispatch_object_t dou;
    dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
    dou._do->do_vtable = vtable;
    return dou._do;
#else
//开辟内存,类对象是vtable
    return _os_object_alloc_realized(vtable, size); 
#endif
}

然后调用_os_object_alloc_realized方法,完成初始化流程。在该函数中,通过调用calloc进行初始化,同时isa指向cls,也就是vtable

inline _os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
    _os_object_t obj;
    dispatch_assert(size >= sizeof(struct _os_object_s));
    while (unlikely(!(obj = calloc(1u, size)))) {
        _dispatch_temporary_resource_shortage();
    }
    obj->os_obj_isa = cls;
    return obj;
}
  • 调用calloc进行初始化。
  • 并将isa指向cls,也就是vtable
  • vtable是队列的根类
解析dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1

不难理解,判断是否为并发队列,如果是,传入DISPATCH_QUEUE_WIDTH_MAX,否则传入1。也就是说,串行队列这里传入1,如果是并发队列,则传入DISPATCH_QUEUE_WIDTH_MAX。其定义见下图:

宽度定义

进入_dispatch_queue_init初始化

进入_dispatch_queue_init构造函数的实现,发现DQF_WIDTH(width);也就是用来确定队列的类型,以此来区分串行队列和并发队列,见下图:

DQF_WIDTH()的使用

到这里我们也就可以确定主队列的类型,在主队列的结构体定义中,DQF_WIDTH(1);,所以主队列是串行队列。

结论:_dispatch_queue_init方法是对dq的属性的设置,如dq_labeldq_priority

dqai的初始化

dp初始化过程中传入的参数vtabledqaidqf,分别是什么,又起到什么作用呢?我们继续分析。

// dqai 创建 - dqa传入的属性串行还是并行
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

_dispatch_lane_create_with_target第一行,就进行了dqai的初始化,其中参数dqa为要创建的队列的类型。查看_dispatch_queue_attr_to_info源码:

_dispatch_queue_attr_to_info

初始化了dqai,并判断dqa的类型,如果是并发队列,则设置并发队列为true,否则默认为串行队列。在调用_dispatch_queue_init对dq进行构造时,对队列类型进行了区分,也就是DQF_WIDTH(width);的传参,串行队列width=1,否则为并发队列。

vtable是什么

vtable可以理解为是一个类,或者说构造队列的模板类vtable初始化流程见下面源码:

        const void *vtable; // - 设置类 类是通过宏定义拼接而成
        if (dqai.dqai_concurrent) {
                // OS_dispatch_##name##_class
                // OS_dispatch_queue_concurrent - 宏定义拼接类类型
                vtable = DISPATCH_VTABLE(queue_concurrent);
        } else {
                vtable = DISPATCH_VTABLE(queue_serial);
        }

dqai在前面创建时会区分队列的类型,根据队列的类型来初始化不同的vtableDISPATCH_VTABLE是一个宏定义的方法,全局搜索DISPATCH_VTABLE的定义,最终找到以下完整的定义流程:

// DISPATCH_VTABLE定义
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)

// vtable symbols - 模板
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))

// 拼接形成类
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class

DISPATCH_VTABLE函数的传参根据不同的队列类型传参不一致。

  • 并发队列
    传参queue_concurrent,最终拼接后,队列类型,也就是对应的类为:OS_dispatch_queue_concurrent
  • 串行队列
    传参queue_serial,最终拼接后,队列类型,也就是对应的类为:OS_dispatch_queue_serial

所以vtable对应的就是队列的类型。通过拼接完成类的定义,这和我们在应用层使用的队列类型是一致的,见下图:

vtable案例

总结:

在底层,会根据上层传入的队列名称lable队列类型进程封装处理。根据类型初始化对应的vtable,也就是对应的队列类模板类)。通过allocinit方法完成队列的内存开辟构造初始化过程,设置队列的对象的isa指向,并完成队列类型的区分。

4. 函数执行

上面已经总结了GCD中的异步函数与同步函数,如下:

  • 同步函数dispatch_sync
  • 异步函数dispatch_async

疑问:那么他们的底层执行逻辑是什么呢?这个模块主要探究这个哦

4.1同步函数

libdispatch.dylib源码中,全局搜索dispatch_sync(dis,找到了同步函数的入口,如下:

//两个参数分别是队列和任务
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

两个参数分别是:队列dq和任务work。继续跟踪,调用_dispatch_sync_f函数,那么进入_dispatch_sync_f,见下图:

//参数分别是:队列 ,任务,宏定义的方法
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
        uintptr_t dc_flags)
{
    _dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}

进入_dispatch_sync_f_inline方法:

static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func, uintptr_t dc_flags)
{
    //主队列或者串行队列
    if (likely(dq->dq_width == 1)) {
        return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
    }

    if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
        DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
    }

    dispatch_lane_t dl = upcast(dq)._dl;
    // Global concurrent queues and queues bound to non-dispatch threads
    // always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
        return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
    }

    if (unlikely(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
    }
//同步执行
    _dispatch_introspection_sync_begin(dl);
    _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
            _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}

封装流程:

  • dispatch_sync
  • _dispatch_sync_f
  • _dispatch_sync_f_inline

参数:

  • dq:队列dq
  • ctxt:任务work
  • func:宏定义的函数_dispatch_Block_invoke
    #define _dispatch_Block_invoke(bb) \ ((dispatch_function_t)((struct Block_layout *)bb)->invoke)

_dispatch_sync_f_inline方法中,哪一行才是我们需要研究的重点呢?先简单分析一下,首先最前面的一段判断流程:

if (likely(dq->dq_width == 1)) {
    return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}

此部分显然是串行队列流程,因为上面已经分析了,dq_width=1时,表示串行队列。那么_dispatch_sync_f_slow_dispatch_sync_recurse会总哪一步呢?我们可以通过下符号断点来确定,通过下符号断点,发现自定义并发队列,其成功走到_dispatch_sync_f_slow中。见下图:

_dispatch_sync_f_slow

进入_dispatch_sync_f_slow方法,继续下符号断点如下:

显然传入的地方不太可能是null,所以大概率会走_dispatch_sync_function_invoke方法,同样采用下符号断点的方式跟踪。见下图:
_dispatch_sync_function_invoke

进入_dispatch_sync_function_invoke方法后,会调用_dispatch_sync_function_invoke_inline方法,见下图:

进入_dispatch_sync_function_invoke_inline实现中:

static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
        dispatch_function_t func)
{
    dispatch_thread_frame_s dtf;
    _dispatch_thread_frame_push(&dtf, dq);
    _dispatch_client_callout(ctxt, func);
    _dispatch_perfmon_workitem_inc();
    _dispatch_thread_frame_pop(&dtf);
}

这就是我们上面说的funcctxtwork任务)对应的函数入口。

  • 函数入口:_dispatch_client_callout

上面的方法有很多哦,我们运行以下案例,bt看以下函数调用栈信息,如下:

函数调用栈信息

发现_dispatch_client_callout方法最终完成了任务的调用执行,进入_dispatch_client_callout方法,如下:

void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    _dispatch_get_tsd_base();
    void *u = _dispatch_get_unwind_tsd();
    if (likely(!u)) return f(ctxt);  //完成函数的调用
    _dispatch_set_unwind_tsd(NULL);
    f(ctxt);
    _dispatch_free_unwind_tsd();
    _dispatch_set_unwind_tsd(u);
}

_dispatch_client_callout方法有很多,但是最终都通过func完成了work的调用执行。

总结:
在同步流程中通过宏定义的函数_dispatch_Block_invoke,也就是func,完成任务work的执行。

4.2 异步函数

libdispatch.dylib源码中,全局搜索dispatch_async(dis,找到了异步函数的入口。见下:

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
//保存block,把work(任务)包装成func保存到dc  -- 任务的封装
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
//并发队列处理
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
4.2.1任务的封装_dispatch_continuation_init

在该方法里,对work进行了封装处理,其中方法_dispatch_continuation_init可以理解为一个任务包装器,进入_dispatch_continuation_init方法中查看实现:

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)
{
//对work完成一次copy操作 -->ctxt
    void *ctxt = _dispatch_Block_copy(work);
//两个路径都是对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);
    }
       //将work包装成一个func
    dispatch_function_t func = _dispatch_Block_invoke(work);
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}

其中_dispatch_Block_invoke这个函数很熟悉,也就是同步函数中的func。但是这里有一个判断,见下面代码:

dispatch_function_t func = _dispatch_Block_invoke(work);

if (dc_flags & DC_FLAG_CONSUME) {

func = _dispatch_call_block_and_release;

}

dc_flags的初始值就是DC_FLAG_CONSUME,所以&处理后,这里会进入条件语句中,并且func被重新设置为_dispatch_call_block_and_release

进入_dispatch_continuation_init_f,如下:

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;   //封装任务
    dc->dc_ctxt = ctxt;   //ctxt = work
    // 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);
}

在这里对将workfunc封装到dc中,同时对任务的优先级进行处理,因为异步函数由于线程或者cpu的调用是异步的,所以这里会对任务的调用优先级进行处理。

回到dispatch_async方法实现,完成任务包装后,调用_dispatch_continuation_async方法进行异步函数的处理流程。如下:

static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
        dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
    if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
        _dispatch_trace_item_push(dqu, dc);
    }
#else
    (void)dc_flags;
#endif
    return dx_push(dqu._dq, dc, qos);
}

dx_push函数三个参数分别为:队列dc(包装的任务)、qos。查看dx_push的宏定义如下:

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

vtable就是上面在分析队列的创建初始化时的模板类,也就是队列对应的类。这里会调用vtable这个类的dq_push方法,直接搜索dq_push,找到其实现:

dq_push的实现

底层为不同类型的队列提供不同的调用入口,比如全局并发队列会调用_dispatch_root_queue_push方法。依次作为入口,全局搜索_dispatch_root_queue_push方法的实现:
_dispatch_root_queue_push

在此流程中,前面只是做一些判断封装处理,最终会走到最后一行代码_dispatch_root_queue_push_inline中,继续跟踪源码流程:

_dispatch_root_queue_push_inline中调用了_dispatch_root_queue_poke方法,_dispatch_root_queue_poke中的核心流程为_dispatch_root_queue_poke_slow,见下图所示:

进入_dispatch_root_queue_poke_slow的实现,发现了_dispatch_root_queues_init()方法,如下图:

进入_dispatch_root_queues_init()方法,在该方法中采用的单例处理,见下图:

单例处理_dispatch_root_queues_init_once里面做了什么呢?跟着进去看看:
_dispatch_root_queues_init_once内部处理

在该方法中进行了线程池的初始化处理工作队列的配置工作队列的初始化等工作,这也就是解释了为什么_dispatch_root_queues_init_once是单例的。单例可以避免重复的初始化
同时这里有一个关键的设置,执行函数的设置,也就是将任务执行的函数被统一设置成了_dispatch_worker_thread2,如下代码:

cfg.workq_cb = _dispatch_worker_thread2;

通过bt打印运行堆栈信息,来验证异步函数最终任务是通过_dispatch_worker_thread2调用的。见下图所示:

函数调用栈信息

总结:
通过跟踪异步处理流程,系统针对不同的队列类型执行不同的dq_push的方法,并通过单例的形式完成了线程池的初始化工作队列的配置等工作,并且底层最终通过_dispatch_worker_thread2完成了异步函数中任务的调用执行。

总结

其实GCD还有很多底层原理这篇文章没有涉及到,如对于异步函数,线程在哪里开辟?底层通过_dispatch_worker_thread2方法完成任务的执行,那么触发调用的位置在哪GCD中单例的逻辑是怎样的?我们下篇文章继续探索看看。

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

推荐阅读更多精彩内容