多线程(二)

线程死锁

线程死锁

当在主线程执行如上代码的时候,产生了死锁,究竟是怎么样的原因呢?在崩溃信息中,看到了有一个#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。查看堆栈信息


全局队列异步函数堆栈

通过图可以看出,有线程相关的操作

以上
死锁:在当前线程(和当前队列相关的)同步的向串行队列里面添加任务,就会死锁。
同步函数:立即执行,阻塞当前线程,不具备开辟子线程的能力
异步函数:异步执行,不会阻塞当前线程,会开辟子线程

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

推荐阅读更多精彩内容