GCD之函数与队列初探

一、前言

在iOS开发过程中,我们知道多线程技术是使用最多的情况,能快速的执行多个调度任务的执行。而在多线程开发过程当中,多线程技术有好几种,其中包括pthreadNSThreadNSOperationGCD,而GCD是整个iOS开发过程中使用最多的也是最安全的一种技术,因为GCD是基于C/C++函数的封装实现,因此线程比较安全,在多线程开发过程中为我们开发者省去了基于考虑线程安全的事情,专注开发。是多线程开发过程中的首选。

然而GCD

*1 是如何来分配多线程的调度任务的?
*2 结构又有哪些?
*3任务的调度过程是如何调度的?

带着这一系列的问题我们开始探索GCD的函数与队列的搭配使用情况。

二、函数

在我们测试的Demo中我们执行一个相关的GCD同步函数dispatch_async,同时向编译器下一个符号断点 简单的打印一个任务

 dispatch_async(conque, ^{
        NSLog(@"12334");
    });

我们会看到当前的断点会定位到系统的libdispatch.dylib dispatch_async:

系统库定位.png

通过以上我们就知道GCD的源码在libdispatch.dylib,带着找到开源的库。怀着一个好奇的心去看看具体的GCD相关函数是如何实现,底层的调用机制又是怎么样的,接下来让我们进入函数的探索环节吧

2.1 dispatch_sync(同步函数)

我们进去到libdispatch.dylib,进行全局的搜索dispatch_sync会找到相应的函数定义

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);
}

我们能看到底层是调用了一个_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是通过封装一个叫_dispatch_sync_f_inline内联函数从而达到相关的同步函数

_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);
    }
    
    _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_invoke_and_complete

_dispatch_sync_invoke_and_complete(dispatch_lane_t dq, void *ctxt,
        dispatch_function_t func DISPATCH_TRACE_ARG(void *dc))
{
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_trace_item_complete(dc);
    _dispatch_lane_non_barrier_complete(dq, 0);
}

再次进入到_dispatch_sync_function_invoke_inline的我们能看到相应的函数调用过程

_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);
}

最终到_dispatch_client_callout

_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);
    }
    @catch (...) {
        objc_terminate();
    }
}

执行步骤如下:

  • 1 先给任务分配一个任务栈
  • 2把当前调度任务进行入栈。让当前线程准备开始调度相关的任务
  • 3 线程调度当前的任务
  • 4 执行完成后把当前任务弹栈。进行相应的释放操作,
总结:同步函数的执行流程是dispatch_async -> _dispatch_sync_f -> _dispatch_sync_f_inline -> _dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline -> _dispatch_client_callout -> f(ctxt);

2.2dispatch_async(异步函数)

再次全局搜索2dispatch_async进入到相关的异步函数的定义如下:

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);
}

我们通过代码知道,异步函数是通过一个包装器进行相应的任务包装,然后进行相应的函数执行:进入到_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);
}

我们知道如果当前是GCD的调用会走_dispatch_trace_item_push,所以进入到源码一探究竟

_dispatch_trace_item_push(dispatch_queue_class_t dqu, dispatch_object_t _tail)
{
    _dispatch_trace_item_push_inline(dqu._dq, _tail._do);
    _dispatch_introspection_queue_push(dqu, _tail);
}

顺着源码进入第一个函数去初探,我们能看到是一个任务队列封装器,目的就是通过赋值和相应的操作以返回一个全新的任务队列。有兴趣的朋友可以自行去研究了一下;而实际调用过程是第二个函数_dispatch_introspection_queue_push

static inline void
_dispatch_introspection_queue_push(dispatch_queue_class_t dqu,
        dispatch_object_t dou)
{
    _dispatch_introspection_queue_item_enqueue(dqu, dou);
}

再次进入底层_dispatch_introspection_queue_item_enqueue

_dispatch_introspection_queue_item_enqueue(dispatch_queue_t dq,
        dispatch_object_t dou)
{
    DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(
            queue_item_enqueue, dq, dou);
    if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_item_enqueue)) {
        _dispatch_introspection_queue_item_enqueue_hook(dq, dou);
    }
}

到最终我们看到调用的关键字段_dispatch_introspection_queue_item_enqueue_hook

_dispatch_introspection_queue_item_enqueue_hook(dispatch_queue_t dq,
        dispatch_object_t dou)
{
    dispatch_introspection_queue_item_s diqi;
    diqi = dispatch_introspection_queue_item_get_info(dq, dou._dc);
    dispatch_introspection_hook_callout_queue_item_enqueue(dq, &diqi);
}

我们看到最终异步函数的调用是通过封装的宏定义函数执行的

#define DISPATCH_INTROSPECTION_HOOK_CALLOUT(h, ...) ({ \
        __typeof__(_dispatch_introspection_hooks.h) _h; \
        _h = _dispatch_introspection_hooks.h; \
        if (unlikely((void*)(_h) != DISPATCH_INTROSPECTION_NO_HOOK)) { \
            _h(__VA_ARGS__); \
        } })
总结:异步函数的执行流程是 dispatch_async -> _dispatch_continuation_async -> _dispatch_trace_item_push -> _dispatch_introspection_queue_push -> _dispatch_introspection_queue_item_enqueue -> _dispatch_introspection_queue_item_enqueue_hook -> DISPATCH_INTROSPECTION_HOOK_CALLOUT

三、队列

我们都知道在iOS开发过程当中或者是任何一门操作系统语言中,队列都是一个很重要的数据结构,它有着FIFO(先进先出)原则,根据操作系统内核的不同,队列又划分为串行队列并发队列
接下来就让我们从不同的地方取分析这两种队列。

3.1 串行队列

串行队列的概念

所有的调度任务进入到任务栈以后,就由CPU统一调度,最先进去的任务先调度,在调度任务未完成之前,其他任务不能被调度。这就是串行队列,也就是相当于排队买票,一个一个来的进行。

串行队列结构.png
串行队列的结构

我们在测试Demo中创建iOS开发过程中的常用几种队列,mainQueue,serialQueue,globalQueue,以及concurrentQueue, 并且答应相关类的结构可知

 dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    // OS_dispatch_queue_concurrent
    dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    // DISPATCH_QUEUE_SERIAL max && 1
    // queue 对象 alloc init class
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 多个 - 集合
    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
    
    NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);

打印结果如下

<OS_dispatch_queue_serial: cooci>-<OS_dispatch_queue_concurrent: cooci>-<OS_dispatch_queue_main: com.apple.main-thread>-<OS_dispatch_queue_global: com.apple.root.default-qos>

所以从上我们知道了主队列也是串行队列,只是不同于一般的串行队列而已,从iOS GCD文档中已经标记的很明白了。那么串行队列的底层源码是如何实现的呢?我们继续探索

我们从以上的打印结果中知道,主队列的打印结果是OS_dispatch_queue_main: com.apple.main-thread,我们进入源码搜索主线程的结果 能看到相应的队列定义

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",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

3.2 并发队列

并发队列的概念:

所有的调度任务进入到任务栈以后,就由CPU统一调度,最先进去的任务先调度,但是由于计算机的CPU 在短暂的时间内可以对多个调度任务进行处理,利用时间片轮转发来进行任务的调度,所以就好像多个调度任务同时执行的意思;

并发队列结构.png
并发队列的结构

我们从以上的打印结果中知道,主队列的打印结果是OS_dispatch_queue_concurrent,我们进入源码搜索主线程的结果 能看到相应的队列定义

struct dispatch_queue_global_s _dispatch_root_queues[] = {
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
            DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.default-qos.overcommit",
        .dq_serialnum = 11,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
        .dq_label = "com.apple.root.user-initiated-qos",
        .dq_serialnum = 12,
    ),

3.3 队列如何创建的并且关联类信息

我们在打印的结果中已经知道串行队列打印的结构是OS_dispatch_queue_serial ,并发队列打印的结构是OS_dispatch_queue_concurrent,接下来就让我们进入源码看看队列是如何关联类对象并且isa指针的,
首先我们进入到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_lane_create_with_target

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)
    //
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;

就这样能进行相关的队列的创建过程;

以上创建过程中如果需要进行相应的串行和并发的判断

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_VTABLE中去看看是如何决定一个队列是串行还是并发的

我们能找到相应的宏定义是

define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)

再次搜索DISPATCH_OBJC_CLASS

我们也知道当前是一个宏定义如下

#define OS_OBJECT_VTABLE(name)      (&OS_OBJECT_CLASS_SYMBOL(name))
#define DISPATCH_OBJC_CLASS(name)   (&DISPATCH_CLASS_SYMBOL(name))

我们再次全局搜索DISPATCH_CLASS_SYMBOL就能找到定义如下

#define OS_OBJECT_EXTRA_VTABLE_SYMBOL(name) _OS_##name##_vtable
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class

从以上的源码定义我们就知道系统是通过拼接名字进行的

通过以上创建的类进行名字拼接就得到我们自己所定义的类。这就是队列创建和关联的过程。

四、总结

以上就是本人对队列的创建和函数底层源码的调用过程的学习,由于libdispatch源码晦涩难懂,所以我只能跟着源码一步步查询和学习,有很多不足之处,请大声多多指教。

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