线程死锁
当在主线程执行如上代码的时候,产生了死锁,究竟是怎么样的原因呢?在崩溃信息中,看到了有一个#0 0x00000001005bac61 in DISPATCH_WAIT_FOR_QUEUE (),去源码中去探索有关的信息。
static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq)
{
uint64_t dq_state = _dispatch_wait_prepare(dq);
if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
"dispatch_sync called on queue"
"already owned by current thread");
}
...
}
DISPATCH_ALWAYS_INLINE
static inline bool
_dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid)
{
return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
// equivalent to _dispatch_lock_owner(lock_value) == tid
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
在源码中,执行了_dq_state_drain_locked_by方法,内部调用了_dispatch_lock_is_locked_by,在_dispatch_lock_is_locked_by中lock_value是传递的当前的队列,tid是当前的线程ID,#define 而DLOCK_OWNER_MASK是((dispatch_lock)0xfffffffc)一个很大的数字。只有当0&一个很大的数字才会等于0,所以lock_value ^ tid = 0,即lock_value = tid ,也就是当前的线程和当前的队列相等的时候,这个等式成立,也就是死锁条件成立。DISPATCH_CLIENT_CRASH信息中也描述的很清楚, "dispatch_sync called on queue" "already owned by current thread",在当前线程同步的向串行队列里面添加任务,就会死锁。
死锁条件
- 在当前线程(和当前队列相关的)同步的向串行队列里面添加任务,就会死锁。
func test(){
print("\(Thread.main)")
let queue = DispatchQueue(label: "tt", qos: .default, autoreleaseFrequency: .inherit, target: nil)
queue.sync {
DispatchQueue.main.sync {
}
}
}
因为同步函数不具备开启线程的能力,所以这一个任务还是添加到了主线程,又因为是同步函数,需要等待整个queue.sync 执行完毕,整个queue.sync 执行完毕,又需要DispatchQueue.main.sync执行完毕,而DispatchQueue.main.sync任务是后加的,需要等queue.sync执行完,就形成了互相等待死锁的状况。
func test(){
print("\(Thread.main)")
let queue = DispatchQueue(label: "tt", qos: .default, autoreleaseFrequency: .inherit)
queue.sync {
queue.sync {
}
}
}
在串行队列queue的同步函数中,继续执行queue的同步任务,就会产生死锁状态。
func test(){
print("\(Thread.main)")
let queue = DispatchQueue(label: "tt", qos: .default, autoreleaseFrequency: .inherit)
let queue2 = DispatchQueue(label: "2", qos: .default, autoreleaseFrequency: .inherit)
queue.sync {
queue2.sync {
}
}
}
而再创建串行队列queue2,在串行queue.sync同步执行任务就不会有死锁,如上述代码所示,这就说明了当前线程必须和串行队列相关,同步添加任务才会造成死锁,也就是一开始的结论在当前线程(和当前队列相关的)同步的向串行队列里面添加任务,就会死锁,和当前队列相关就是线程执行的任务是从相关队列里面去出来的。
同步函数和异步函数
同步函数:立即执行,阻塞当前线程,不具备开辟子线程的能力
异步函数:异步执行,不会阻塞当前线程,会开辟子线程
那么在同步函数和异步函数关于任务的执行时机,和线程的相关操作,带着这两个问题去探究一下GCD源码。
- 同步函数GCD源码探究
DISPATCH_NOINLINE
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的dispatch_block_t work就是我们在使用时的代码块,那么work的执行时机就是我们的任务执行的时机,work使用的地方有
unlikely(_dispatch_block_has_private_data(work))这个是小概率事件,是系统级别的判断。
_dispatch_sync_f的参数 _dispatch_Block_invoke(work),这是一层封装,把work这个block封装为Block_layout的结构体,在这里执行编写的任务block代码
DISPATCH_NOINLINE
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_ALWAYS_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_f中,任务blok参数work,被封装为Block_layout,以参数func传入到_dispatch_sync_f_inline中,也就是func的执行就是最终任务block执行时机。由于这上面的代码比较复杂,靠阅读不知道会执行哪个方法,所以通过加符号断点的方式,来看最终执行哪一步。将使用了func的方法,_dispatch_barrier_sync_f,_dispatch_sync_f_slow,
_dispatch_sync_recurse,_dispatch_sync_invoke_and_complete加入符号断点,然后调用dispatch_sync(dispatch_get_global_queue(0, 0), ^{ });,通过断点,来到了_dispatch_sync_f_slow,有两个地方执行了func
- _dispatch_sync_function_invoke
DISPATCH_NOINLINE
static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}
DISPATCH_ALWAYS_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);
}
在_dispatch_sync_function_invoke-> _dispatch_sync_function_invoke_inline-> _dispatch_client_callout->然后执行了任务
这是全局同步队列执行的过程。
同步串行队列则是执行了_dispatch_barrier_sync_f,在_dispatch_lane_barrier_sync_invoke_and_complete执行了任务。在过程中没有看到线程相关的东西,而且并没有保存任务,是立即执行了任务。
- 异步函数GCD 源码探究
#ifdef __BLOCKS__
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;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
DISPATCH_ALWAYS_INLINE
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)
{
void *ctxt = _dispatch_Block_copy(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);
}
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_async中调用了_dispatch_continuation_init,该方法对任务进行了拷贝_dispatch_Block_copy(work)这个是拷贝整个任务包括任务的数据,在对任务进行_dispatch_Block_invoke封装后,通过调用_dispatch_continuation_init_f将任务进行了保存。保存block在需要调用的时候取出,就是函数式编程。整个_dispatch_continuation_init过程生成了一个qos,然后执行了_dispatch_continuation_async。查看堆栈信息
通过图可以看出,有线程相关的操作
以上
死锁:在当前线程(和当前队列相关的)同步的向串行队列里面添加任务,就会死锁。
同步函数:立即执行,阻塞当前线程,不具备开辟子线程的能力
异步函数:异步执行,不会阻塞当前线程,会开辟子线程