OC底层知识点之-多线程(三)GCD中篇

异步函数

先看下dispatch_async的底层实现

上图我们发现有两个主要方法:

  • 1._dispatch_continuation_init这个方法上篇最后讲了用处:就是任务包装,将work(任务执行)绑定到dc的dc_ctxt中,将方法绑定到dc的dc_func中
  • 2._dispatch_continuation_async是并发处理函数,主要执行block回调

_dispatch_continuation_init方法上篇讲了,这里不再过多陈述。我们看下_dispatch_continuation_async方法。

_dispatch_continuation_async

我们进入_dispatch_continuation_async的方法实现

方法中的关键代码为2659行:dx_push(dqu._dq, dc, qos);(原因:这个方法的结果作为返回值返回,前面只是对dqu进行处理)。

dx_push

dx_push是个宏定义,如下图所示:

我们看到dx_push最后会执行dq_push方法,而dq_push方法进行搜索时发现有很多

通过上图我们可以看到,.dq_push会根据队列的不同类型,执行不同的函数。我们注意下673-682行,662-671行,我们看到.do_type分别为DISPATCH_QUEUE_CONCURRENT_TYPE和DISPATCH_QUEUE_SERIAL_TYPE,类似并发队列和串行队列。我们通过发号断点看看并发队列是不是走_dispatch_lane_concurrent_push串行队列是不是走_dispatch_lane_push

先打断点:
    dispatch_queue_t conque = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(conque, ^{
        NSLog(@"12334");
    });

运行代码,在dispatch_async处打断点,运行到断点处,在讲符号断点打开,下一步,发现确实走到了_dispatch_lane_concurrent_push方法

下面我们看下串行队列。

    dispatch_queue_t serial = dispatch_queue_create("Lj", DISPATCH_QUEUE_SERIAL);
   dispatch_async(serial, ^{
       NSLog(@"12334");
   });

和验并行一样,发现确实执行了_dispatch_lane_push方法

上面验证说明了.dq_push确实会根据队列的不同类型,执行不同的函数

_dispatch_lane_concurrent_push

下面我们看下_dispatch_lane_concurrent_push方法,源码实现

我们看到_dispatch_lane_concurrent_push源码分两步实现:

  • 1._dispatch_continuation_redirect_push
  • 2._dispatch_lane_push

通过符号断点运行,发现并发队列会执行_dispatch_continuation_redirect_push方法

源码中搜索_dispatch_continuation_redirect_push

我们发现这个_dispatch_continuation_redirect_push也执行的dx_push。上面我们分析_dispatch_continuation_async就会调用dx_push,此处也调用,会形成递归吗?答案肯定是不会!原因在前面队列创建时可知,队列也是一个对象,有父类,根类。所以此时调用dx_push是调用的父类或者根类方法

上面说了dx_push会调用父类或者根类方法,上面说了dx_push会调用dq_push,上面我们罗列了dq_push会调用哪些方法,根类方法在684-705行,此时调用的方法为_dispatch_root_queue_push,我们打断点,看看是不是会走这个方法

通过上面图我们知道并发队列会调用_dispatch_root_queue_push方法,所以我们上面说的dx_push调用会调用父类或者根类是对的

我们来看下_dispatch_root_queue_push源码,已将将无关紧要的隐藏了

我们通过_dispatch_root_queue_push看到执行顺序:_dispatch_root_queue_push->_dispatch_root_queue_push_inline->_dispatch_root_queue_poke->_dispatch_root_queue_poke_slow,我们主要看下_dispatch_root_queue_poke_slow的源码

这个方法我们把不主要的方法给隐藏了,我们说下主要方法:

  • 1.通过do-while循环创建线程,使用pthread_create方法创建(第6223行)
  • 2.通过_dispatch_root_queues_init方法注册回调(第6233行)

下面我们看下_dispatch_root_queues_init源码:

我们看到调用了dispatch_once_f,这个是个单例(后续会做个单例的底层分析,这里不做说明),其中传入的func是_dispatch_root_queues_init_once,下面我们再查看下_dispatch_root_queues_init_once源码实现,我们看主要的。

上图我们看到进入_dispatch_root_queues_init_once的源码,其内部不同事务的调用句柄是_dispatch_worker_thread2(第7641行,第7650行,7666行)。

通过上面可以知道,其block回调执行的调用路径为:_dispatch_root_queues_init_once -> _dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release 下面我们通过打印堆栈信息来验证一下

通过打印得出我们的推测是对的。

说明

这里需要说明一点的是,单例的block回调异步函数的block回调是不同的

  • 1.单例中block回调中的func是_dispatch_Block_invoke(block)
  • 2.而异步函数中,block回调中的func是dispatch_call_block_and_release

总结

综上所述,异步函数的底层分析如下

  • 1.【准备工作】:首先,将异步任务拷贝并封装,并设置回调函数func
  • 2.【block回调】:底层通过dx_push递归会重定向到根队列,然后通过pthread_creat创建线程,最后通过dx_invoke执行block回调(注意dx_push 和 dx_invoke 是成对的)

同步函数

进入dispatch_async源码实现,其底层实现是通过栅栏函数实现的(栅栏函数后面说明)

上面我们可以看到dispatch_sync->_dispatch_sync_f->_dispatch_sync_f_inline顺序执行,看下_dispatch_sync_f_inline的方法

  • 1.dq->dq_width等于1表示串行队列
  • 2._dispatch_barrier_sync_f为栅栏函数,可以看到同步函数的底层实现其实是同步栅栏函数
  • 3._dispatch_sync_f_slow死锁,我们之前验证相互等待的死锁报错时,就执行了这个方法

我们看下_dispatch_sync_f_slow的具体方法实现,调用_dispatch_sync_f_slow方法,表明当前的队列是挂起,阻塞的

往一个队列加入任务,会push加入主队列,进入_dispatch_trace_item_push

dispatch_trace_item_push方法执行完成后,就会执行__DISPATCH_WAIT_FOR_QUEUE_。下面我们看下DISPATCH_WAIT_FOR_QUEUE

_dispatch_wait_prepare:判断dq是否为正在等待的主队列,然后给出一个状态state,然后将dq的状态和当前任务依赖的队列进行匹配

接着执行_dq_state_drain_locked_by->_dispatch_lock_is_locked_by

如果当前等待和正在执行的是同一个队列(判断线程的id是否相等),如果是同一个队列即判断为死锁

同步函数+并发队列

在_dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline源码中,主要有三个步骤:

  • 1.将任务压入队列:_dispatch_thread_frame_push
  • 2.执行任务的block回调:_dispatch_client_callout
  • 3.将任务出队:_dispatch_thread_frame_pop

从实现中可以看出,是先将任务push队列中,然后执行block回调,再将任务pop,所以任务是顺序执行的

总结

同步函数的底层实现:

  • 1.同步函数的底层实现实际是同步栅栏函数
  • 2.同步函数中如果当前正在执行的队列和等待的是同一个队列形成相互等待的局面,则会造成死锁

写到最后

这篇文章主要讲了dispatch_async(异步任务)和dispatch_sync(同步任务)底层方法调用过程,也解释也死锁的底层判断。下篇我们主要研究下栅栏函数,单例,信号量。

收录

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

推荐阅读更多精彩内容