异步函数
先看下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
也执行的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源码,已将将无关紧要的隐藏了通过上面图我们知道
并发队列会调用_dispatch_root_queue_push方法
,所以我们上面说的dx_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_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方法,表明当前的队列是挂起,阻塞的
dispatch_trace_item_push方法执行完成后,就会执行__DISPATCH_WAIT_FOR_QUEUE_。下面我们看下DISPATCH_WAIT_FOR_QUEUE
接着执行_dq_state_drain_locked_by->_dispatch_lock_is_locked_by_dispatch_wait_prepare:
判断dq是否为正在等待的主队列
,然后给出一个状态state,然后将dq的状态和当前任务依赖的队列进行匹配
如果当前等待和正在执行的是同一个队列(判断线程的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(同步任务)底层方法调用过程,也解释也死锁的底层判断。下篇我们主要研究下栅栏函数,单例,信号量。
收录