欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

iOS开发探索多线程GCD任务示例详解,

来源: 开发者 投稿于  被查看 29772 次 评论:64

iOS开发探索多线程GCD任务示例详解,


目录
  • 引言
    • 同步任务
    • 死锁
    • 异步任务
  • 总结

    引言

    在上一篇文章中,我们探寻了队列是怎么创建的,串行队列和并发队列之间的区别,接下来我们在探寻一下GCD的另一个核心 - 任务

    同步任务

    void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
    

    我们先通过lldb查看其堆栈信息,分别查看其正常运行和死锁状态的信息

    我们再通过源码查询其实现

    #define _dispatch_Block_invoke(bb) ((dispatch_function_t)((struct Block_layout *)bb)->invoke)
    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_Block_invoke将函数包装成了一个block,后继续向下传递,也就是说我们的代码是通过这个block来进行的执行,继续查询其的传递

    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_inline中存在我们查看的堆栈信息时的两个函数_dispatch_sync_f_slow、_dispatch_sync_invoke_and_complete

    static void _dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt, dispatch_function_t func, uintptr_t top_dc_flags, dispatch_queue_class_t dqu, uintptr_t dc_flags) {
        ...省略部分...
        struct dispatch_sync_context_s dsc = {
            ...省略部分...
            .dsc_func    = func,
        };
        __DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);
        ...省略部分...
        _dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags DISPATCH_TRACE_ARG(&dsc));
    }
    static void _dispatch_sync_invoke_and_complete_recurse(dispatch_queue_class_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags DISPATCH_TRACE_ARG(void *dc)) {
        _dispatch_sync_function_invoke_inline(dq, ctxt, func);
        _dispatch_trace_item_complete(dc);
        _dispatch_sync_complete_recurse(dq._dq, NULL, dc_flags);
    }
    
    static void _dispatch_sync_invoke_and_complete(dispatch_lane_t dq, void *ctxt, dispatch_function_t func DISPATCH_TRACE_ARG(void *dc)) {
        _dispatch_sync_function_invoke_inline(dq, ctxt, func);
        _dispatch_trace_item_complete(dc);
        _dispatch_lane_non_barrier_complete(dq, 0);
    }
    

    我们发现,两种堆栈信息的函数的func这个block都是传递给的_dispatch_sync_function_invoke_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);
    }
    void _dispatch_client_callout(void *ctxt, dispatch_function_t f) {
        _dispatch_get_tsd_base();
        void *u = _dispatch_get_unwind_tsd();
        if (likely(!u)) return f(ctxt);
        _dispatch_set_unwind_tsd(NULL);
        f(ctxt); // 调用block函数,执行任务
        _dispatch_free_unwind_tsd();
        _dispatch_set_unwind_tsd(u);
    }
    

    最终通过_dispatch_client_callout函数执行的我们的block代码,和我们正常执行的堆栈信息相符合。

    通过我们的源码的探索,我们发现了在同步任务中并没有任何关于开辟线程的操作,而且任务并没有保存而是直接执行的。

    死锁

    我们在获取的堆栈信息发现了崩溃调用的函数是__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"); // 当前线程已存在这个同步队列
        }
        ...省略部分...
    }
    // crash条件
    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);
    }
    static inline bool _dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid) {
        // equivalent to _dispatch_lock_owner(lock_value) == tid
        // lock_value 当前队列, tid 当前线程
        return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
    }
    

    通过这里可以看到 崩溃的条件:串行队列,当前队列已在当前线程中。

    异步任务

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

    一样先通过lldb查看一下堆栈信息

    很明显的和同步任务的区别是里面有pthread.dylib的调用,我们还是来通过源码看一下吧。

    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);
    }
    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);
        ...省略部分...
        dispatch_function_t func = _dispatch_Block_invoke(work);
    }
    static inline dispatch_qos_t _dispatch_continuation_init_f(dispatch_continuation_t dc, dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f, dispatch_block_flags_t flags, uintptr_t dc_flags) {
        pthread_priority_t pp = 0;
        dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
        dc->dc_func = f;
        dc->dc_ctxt = ctxt;
        ……
        _dispatch_continuation_voucher_set(dc, flags);
        return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
    }
    

    异步任务将任务先用_dispatch_continuation_init进行了copy操作,保存了任务,同时和同步函数一样将任务用_dispatch_Block_invoke进行了封装,然后将copy的任务和封装的block赋值给dispatch_continuation_t dc,也就是相当于保存了队列中添加的任务,最终返回一个dispatch_qos_t的对象qos。

    #define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
    static inline void _dispatch_continuation_async(dispatch_queue_class_t dqu, dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags) {
        return dx_push(dqu._dq, dc, qos);
    }
    

    源码中全局搜索dq_push,我们在熟悉的文件Dispatch Source/init.c中找到了每种队列对应的dq_push

    DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_serial, lane,
        .do_type        = DISPATCH_QUEUE_SERIAL_TYPE,
        ......
        .dq_push        = _dispatch_lane_push,
    );
    DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
        .do_type        = DISPATCH_QUEUE_CONCURRENT_TYPE,
        ......
        .dq_push        = _dispatch_lane_concurrent_push,
    );
    DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
        .do_type        = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
        ......
        .dq_push        = _dispatch_root_queue_push,
    );
    DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_main, lane,
        .do_type        = DISPATCH_QUEUE_MAIN_TYPE,
        ......
        .dq_push        = _dispatch_main_queue_push,
    );
    

    我们通过最常用的global的_dispatch_root_queue_push来进行探索

    void _dispatch_root_queue_push(dispatch_queue_global_t rq, dispatch_object_t dou, dispatch_qos_t qos) {
    ...省略部分...
        #if HAVE_PTHREAD_WORKQUEUE_QOS
            if (_dispatch_root_queue_push_needs_override(rq, qos)) {
                return _dispatch_root_queue_push_override(rq, dou, qos);
            }
        #else
            (void)qos;
        #endif
        _dispatch_root_queue_push_inline(rq, dou, dou, 1);
    }
    static void _dispatch_root_queue_push_override(dispatch_queue_global_t orig_rq, dispatch_object_t dou, dispatch_qos_t qos) {
        ......
        _dispatch_root_queue_push_inline(rq, dc, dc, 1);
    }
    

    我们可以看到其内部是调用的_dispatch_root_queue_push_inline函数,进一步说调用_dispatch_root_queue_poke_slow

    static void _dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor) {
        ......
        _dispatch_root_queues_init();
        ...利用线程池调度任务等相关代码...
    }
    static inline void _dispatch_root_queues_init(void) {
        dispatch_once_f(&_dispatch_root_queues_pred, NULL,
        _dispatch_root_queues_init_once);
    }
    

    在_dispatch_root_queues_init_once中进行了线程对任务的调度

    _dispatch_worker_thread2, _dispatch_worker_thread2 ->

    _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline ->

    _dispatch_continuation_invoke_inline,_dispatch_root_queue_poke_slow

    进行了线程池的相关操作

    也就是我们在堆栈信息中pthread.dylib的调用的原因。这些异步调度我们已经无法进行下一步查看了,所以还是看回我们的堆栈信息,很明显函数的执行仍是通过_dispatch_client_callout进行的执行。

    总结

    • 同步任务:任务立即执行,无线程相关操作,会阻塞当前线程
    • 异步任务:保存任务,进行线程相关操作,可以开辟子线程,不会阻塞当前线程
    • 死锁:在当前线程同步(和当前队列相关)同步的向里面添加任务,就会死锁

    以上就是iOS开发探索多线程GCD任务示例详解的详细内容,更多关于iOS开发多线程GCD任务的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • iOS开发探索多线程GCD队列示例详解
    • IOS开发之多线程NSThiread GCD NSOperation Runloop
    • iOS 多线程总结之GCD的使用详解
    • iOS开发探索多线程GCD常用函数

    用户评论