iOS底层探索之多线程(六)—GCD源码分析(sync 同步函数、async 异步函数)

回顾

在上篇博客对GCD的不同的队列继续了底层的源码探索分析, 那么本篇博客将继续对GCD的函数继续源码分析。

多线程.png

1. sync 同步函数

我们都知道 GCD底层是用C写的,封装了 block函数来执行添加的任务,那么这个 block底层是如何封装的呢?

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"GCD函数分析");
   });

在源码里面搜索dispatch_sync

dispatch_sync

我们看的是block也就是第二个参数work,直接看 work去哪里了就行,直接定位在最后一行代码

_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);

搜索_dispatch_Block_invoke

_dispatch_Block_invoke
  • 来自一个dispatch_function_t类型的,结构体 Block_layout *invoke

那么现在去看看这 block的包装函数_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

_dispatch_sync_f_inline

_dispatch_sync_f_inline里面if判断太多了,不知道该看哪个了,改怎么办呢?这个源码是不能编译运行的!
改怎么办呢???

靓仔,不要慌!我们可以在调用函数的地方,下不同的符号断点,看看走哪个😊

  • 下符断点


    下符合断点
  • 运行看看走哪个
    通过符合断点定位

    通过下符合断点,很清晰的可以看到,走了_dispatch_sync_f_slow,也就是下面图中代码处:
    通过符合断点定位到此处

然后继续在源码里面搜索_dispatch_sync_f_slow

  • _dispatch_sync_f_slow

    _dispatch_sync_f_slow

    这又不知道该走哪里,再次下符合断点,发现走到了_dispatch_sync_function_invoke执行,那么再继续搜索

  • _dispatch_sync_function_invoke

    _dispatch_sync_function_invoke

    我们找谁使用了这 ctxt、func 两个参数,发现是这句代码
    _dispatch_client_callout(ctxt, func)那么现在去搜索一下

  • _dispatch_client_callout

    _dispatch_client_callout

    把自己传入返回,和 block的底层是一样的调用方式,这也就说明了在调用_dispatch_client_callout的时候,异步函数会执行 block,验证如下:
    打印调用堆栈

    通过断点block函数执行体处,再通过 bt打印调用堆栈,可以很明显的看到,在_dispatch_client_callout 函数执行后,才会执行block函数体内。

这一波操作,就很细节,666,还有谁能把GCD源码探索的这么清新脱俗,45 度仰望天空,我这该死的无处安放的魅力!

666

2. asycn 异步函数

异步也是一样, 那么我们来搜索一波

  • dispatch_async
    dispatch_async

    还是一样找到 work 在哪里使用了,由此定位到qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags)这句代码,再搜索_dispatch_continuation_init
  • _dispatch_continuation_init
    _dispatch_continuation_init
  • 可以发现如下代码,对work处理,返回了func,再传入_dispatch_continuation_init_f中,继续搜索_dispatch_continuation_init_f
    _dispatch_continuation_init_f

在前面的那个,是直接返回了,但是这里做了一层包装

dc->dc_func = f;
dc->dc_ctxt = ctxt;

包装完了,还有一个对优先级的处理

return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
  • _dispatch_continuation_priority_set

_dispatch_continuation_priority_set

我们再回到dispatch_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;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

这里面就是对执行的任务设置了优先级的封装,那么苹果为什么要在异步的里面做这么个封装呢?

  • 异步函数代表异步调用
  • 异步是无序的调用
  • 异步的回调也是异步的,会根据 CPU的调度在适当的时候异步回调
  • 也是函数式编程的一中体现,就是在需要的时候进行回调

但是这里最重要的还是最后一行代码

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

这里就会迷失方向了,这个dx_push是个什么东西呢?搜索了下,找到了下面这个宏定义

dx_push

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

在宏定义里面dx_vtable不是我们需要看的,我们去找dq_push,这个第三个参数 z 就是 qos,看看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_push_inline

    继续跟踪_dispatch_root_queue_poke
  • _dispatch_root_queue_poke
    _dispatch_root_queue_poke
  • _dispatch_root_queue_poke_slow
    _dispatch_root_queue_poke_slow

    这么多代码,看了一遍没有找到什么关键信息,在 6295行代码,_dispatch_root_queues_init方法里面有关键信息
_dispatch_root_queues_init(void)
{
    dispatch_once_f(&_dispatch_root_queues_pred, NULL,
            _dispatch_root_queues_init_once);
}

这里是一个dispatch_once_f单例,有个参数_dispatch_root_queues_init_once,继续搜索

  • _dispatch_root_queues_init_once
    _dispatch_root_queues_init_once

    在该方法中进行了线程池的初始化处理、工作队列的配置、初始化等工作,这也就是解释了为什么_dispatch_root_queues_init_once是单例的。单例可以避免重复的初始化。

同时这里有一个关键的设置,执行函数的设置,也就是将任务执行的函数被统一设置成了_dispatch_worker_thread2 ,如下代码:

cfg.workq_cb = _dispatch_worker_thread2;
        r = pthread_workqueue_setup(&cfg, sizeof(cfg));

通过bt打印程序的运行堆栈信息,来验证异步函数最终任务是通过_dispatch_worker_thread2调用的

_dispatch_worker_thread2

控制台打印的堆栈信息,和我们探索推理的结果是,一模模一样样😁,就问靓仔你服不服!哈哈😁!
还有谁

3. 总结

  • GCD源码难懂,可以通过一些返回值和关键信息推理
  • 通过 下符号断点,追踪代码调用逻辑
  • 通过 bt 打印堆栈验证探索结果

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

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