iOS開發(fā)探索多線程GCD任務(wù)示例詳解
引言
在上一篇文章中,我們探尋了隊列是怎么創(chuàng)建的,串行隊列和并發(fā)隊列之間的區(qū)別,接下來我們在探尋一下GCD的另一個核心 - 任務(wù)
同步任務(wù)
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
我們先通過lldb查看其堆棧信息,分別查看其正常運行和死鎖狀態(tài)的信息
我們再通過源碼查詢其實現(xiàn)
#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將函數(shù)包裝成了一個block,后繼續(xù)向下傳遞,也就是說我們的代碼是通過這個block來進行的執(zhí)行,繼續(xù)查詢其的傳遞
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))); }
我們發(fā)現(xiàn)_dispatch_sync_f_inline中存在我們查看的堆棧信息時的兩個函數(shù)_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); }
我們發(fā)現(xiàn),兩種堆棧信息的函數(shù)的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); // 調(diào)用block函數(shù),執(zhí)行任務(wù) _dispatch_free_unwind_tsd(); _dispatch_set_unwind_tsd(u); }
最終通過_dispatch_client_callout函數(shù)執(zhí)行的我們的block代碼,和我們正常執(zhí)行的堆棧信息相符合。
通過我們的源碼的探索,我們發(fā)現(xiàn)了在同步任務(wù)中并沒有任何關(guān)于開辟線程的操作,而且任務(wù)并沒有保存而是直接執(zhí)行的。
死鎖
我們在獲取的堆棧信息發(fā)現(xiàn)了崩潰調(diào)用的函數(shù)是__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; }
通過這里可以看到 崩潰的條件:串行隊列,當前隊列已在當前線程中。
異步任務(wù)
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
一樣先通過lldb查看一下堆棧信息
很明顯的和同步任務(wù)的區(qū)別是里面有pthread.dylib的調(diào)用,我們還是來通過源碼看一下吧。
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); }
異步任務(wù)將任務(wù)先用_dispatch_continuation_init進行了copy操作,保存了任務(wù),同時和同步函數(shù)一樣將任務(wù)用_dispatch_Block_invoke進行了封裝,然后將copy的任務(wù)和封裝的block賦值給dispatch_continuation_t dc,也就是相當于保存了隊列中添加的任務(wù),最終返回一個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中找到了每種隊列對應(yīng)的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); }
我們可以看到其內(nèi)部是調(diào)用的_dispatch_root_queue_push_inline函數(shù),進一步說調(diào)用_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(); ...利用線程池調(diào)度任務(wù)等相關(guān)代碼... } 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中進行了線程對任務(wù)的調(diào)度
_dispatch_worker_thread2, _dispatch_worker_thread2 ->
_dispatch_root_queue_drain -> _dispatch_continuation_pop_inline ->
_dispatch_continuation_invoke_inline,_dispatch_root_queue_poke_slow
進行了線程池的相關(guān)操作
也就是我們在堆棧信息中pthread.dylib的調(diào)用的原因。這些異步調(diào)度我們已經(jīng)無法進行下一步查看了,所以還是看回我們的堆棧信息,很明顯函數(shù)的執(zhí)行仍是通過_dispatch_client_callout進行的執(zhí)行。
總結(jié)
- 同步任務(wù):任務(wù)立即執(zhí)行,無線程相關(guān)操作,會阻塞當前線程
- 異步任務(wù):保存任務(wù),進行線程相關(guān)操作,可以開辟子線程,不會阻塞當前線程
- 死鎖:在當前線程同步(和當前隊列相關(guān))同步的向里面添加任務(wù),就會死鎖
以上就是iOS開發(fā)探索多線程GCD任務(wù)示例詳解的詳細內(nèi)容,更多關(guān)于iOS開發(fā)多線程GCD任務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS 將系統(tǒng)自帶的button改裝成上圖片下文字的樣子
這篇文章主要介紹了 iOS 將系統(tǒng)自帶的button改裝成上圖片下文字的樣子,代碼是通過繼承UIButton,然后再重寫layoutSubviews方法,對自帶的圖片和titleLabel進行重新的layout。下面通過本文給大家分享下實現(xiàn)代碼2016-12-12詳解使用Xcode7的Instruments檢測解決iOS內(nèi)存泄露(最新)
本篇文章主要介紹使用Xcode7的Instruments檢測解決iOS內(nèi)存泄露(最新)的相關(guān)資料,需要的朋友可以參考下2017-09-09iOS應(yīng)用中使用Auto Layout實現(xiàn)自定義cell及拖動回彈
這篇文章主要介紹了iOS應(yīng)用中使用Auto Layout實現(xiàn)自定義cell及拖動回彈的方法,自定義UITableViewCell并使用Auto Layout對其進行約束可以方便地針對多尺寸屏幕進行調(diào)整,代碼為Swift語言,需要的朋友可以參考下2016-03-03iOS schem與Universal Link 調(diào)試時踩坑解決記錄
這篇文章主要為大家介紹了iOS schem與Universal Link 調(diào)試時踩坑解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01