本文是隊列創(chuàng)建、同步/異步函數(shù)、單例、信號量以及調(diào)度組的底層原理分析
隊列創(chuàng)建
在上一篇文章GCD 之 函數(shù)與隊列中,我們理解了隊列與函數(shù),知道隊列的創(chuàng)建時通過GCD中的dispatch_queue_create方法,下面我們在libdispatch.dylib去探索隊列是如何創(chuàng)建的(下載鏈接)
底層源碼分析
- 在源碼中搜索
dispatch_queue_create
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr, DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
- 進入
_dispatch_lane_create_with_target(
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// dqai 創(chuàng)建 -
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//第一步:規(guī)范化參數(shù),例如qos, overcommit, tq
...
//拼接隊列名稱
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) { //vtable表示類的類型
// OS_dispatch_queue_concurrent
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
....
//創(chuàng)建隊列,并初始化
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // alloc
//根據(jù)dqai.dqai_concurrent的值,就能判斷隊列 是 串行 還是并發(fā)
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
//設(shè)置隊列l(wèi)abel標(biāo)識符
dq->dq_label = label;//label賦值
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos, dqai.dqai_relpri);//優(yōu)先級處理
...
//類似于類與元類的綁定,不是直接的繼承關(guān)系,而是類似于模型與模板的關(guān)系
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;//研究dq
}
_dispatch_lane_create_with_target 分析
-
【第一步】通過
_dispatch_queue_attr_to_info方法傳入dqa(即隊列類型,串行、并發(fā)等)創(chuàng)建dispatch_queue_attr_info_t類型的對象dqai,用于存儲隊列的相關(guān)屬性信息 -
【第二步】設(shè)置隊列相關(guān)聯(lián)的屬性,例如服務(wù)質(zhì)量qos等
【第三步】通過
DISPATCH_VTABLE拼接隊列名稱,即vtable,其中DISPATCH_VTABLE是宏定義,如下所示,所以隊列的類型是通過OS_dispatch_+隊列類型queue_concurrent拼接而成的-
串行隊列類型:OS_dispatch_queue_serial,驗證如下
并發(fā)隊列類型:OS_dispatch_queue_concurrent,驗證如下
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
??
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
??
#define DISPATCH_CLASS(name) OS_dispatch_##name
第四步】通過alloc+init初始化隊列,即dq,其中在_dispatch_queue_init傳參中根據(jù)dqai.dqai_concurrent的布爾值,就能判斷隊列 是 串行 還是并發(fā),而 vtable表示隊列的類型,說明隊列也是對象
-
進入
_dispatch_object_alloc -> _os_object_alloc_realized方法中設(shè)置了isa的指向,從這里可以驗證隊列也是對象的說法
進入_dispatch_queue_init方法,隊列類型是dispatch_queue_t,并設(shè)置隊列的相關(guān)屬性
【第五步】通過_dispatch_trace_queue_create對創(chuàng)建的隊列進行處理,其中_dispatch_trace_queue_create是_dispatch_introspection_queue_create封裝的宏定義,最后會返回處理過的_dq
進入_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info中可以看出,與我們自定義的類還是有所區(qū)別的,創(chuàng)建隊列在底層的實現(xiàn)是通過模板創(chuàng)建的
總結(jié)
隊列創(chuàng)建方法
dispatch_queue_create中的參數(shù)二(即隊列類型),決定了下層中max & 1(用于區(qū)分是 串行 還是 并發(fā)),其中1表示串行queue也是一個對象,也需要底層通過alloc + init 創(chuàng)建,并且在alloc中也有一個class,這個class是通過宏定義拼接而成,并且同時會指定isa的指向創(chuàng)建隊列在底層的處理是通過模板創(chuàng)建的,其類型是dispatch_introspection_queue_s結(jié)構(gòu)體
dispatch_queue_create底層分析流程如下圖所示
函數(shù) 底層原理分析
主要是分析 異步函數(shù)dispatch_async 和 同步函數(shù)dispatch_sync
異步函數(shù)
進入dispatch_async的源碼實現(xiàn),主要分析兩個函數(shù)
_dispatch_continuation_init:任務(wù)包裝函數(shù)_dispatch_continuation_async:并發(fā)處理函數(shù)
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)//work 任務(wù)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
// 任務(wù)包裝器(work在這里才有使用) - 接受work - 保存work - 并函數(shù)式編程
// 保存 block
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
//并發(fā)處理
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
_dispatch_continuation_init 任務(wù)包裝器
進入_dispatch_continuation_init源碼實現(xiàn),主要是包裝任務(wù),并設(shè)置線程的回程函數(shù),相當(dāng)于初始化
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);//拷貝任務(wù)
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);//封裝work - 異步回調(diào)
if (dc_flags & DC_FLAG_CONSUME) {
func = _dispatch_call_block_and_release;//回調(diào)函數(shù)賦值 - 同步回調(diào)
}
return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
主要有以下幾步
通過
_dispatch_Block_copy拷貝任務(wù)通過
_dispatch_Block_invoke封裝任務(wù),其中_dispatch_Block_invoke是個宏定義,根據(jù)以上分析得知是異步回調(diào)
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
如果是
同步的,則回調(diào)函數(shù)賦值為_dispatch_call_block_and_release-
通過
_dispatch_continuation_init_f方法將回調(diào)函數(shù)賦值,即f就是func,將其保存在屬性中
_dispatch_continuation_async 并發(fā)處理
這個函數(shù)中,主要是執(zhí)行block回調(diào)
- 進入
_dispatch_continuation_async的源碼實現(xiàn)
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);//跟蹤日志
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);//與dx_invoke一樣,都是宏
}
- 其中的關(guān)鍵代碼是
dx_push(dqu._dq, dc, qos),dx_push是宏定義,如下所示
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
-
而其中的
dq_push需要根據(jù)隊列的類型,執(zhí)行不同的函數(shù)
符號斷點調(diào)試執(zhí)行函數(shù)
- 運行demo,通過
符號斷點,來判斷執(zhí)行的是哪個函數(shù),由于是并發(fā)隊列,通過增加_dispatch_lane_concurrent_push符號斷點,看看是否會走到這里
dispatch_queue_t conque = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
NSLog(@"異步函數(shù)");
});
-
運行發(fā)現(xiàn),走的確實是
_dispatch_lane_concurrent_push
進入_dispatch_lane_concurrent_push源碼,發(fā)現(xiàn)有兩步,繼續(xù)通過符號斷點_dispatch_continuation_redirect_push和_dispatch_lane_push調(diào)試,發(fā)現(xiàn)走的是_dispatch_continuation_redirect_push
進入_dispatch_continuation_redirect_push源碼,發(fā)現(xiàn)又走到了dx_push,即遞歸了,綜合前面隊列創(chuàng)建時可知,隊列也是一個對象,有父類、根類,所以會遞歸執(zhí)行到根類的方法
接下來,通過根類的_dispatch_root_queue_push符號斷點,來驗證猜想是否正確,從運行結(jié)果看出,完全是正確的
-
進入
_dispatch_root_queue_push -> _dispatch_root_queue_push_inline ->_dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow源碼,經(jīng)過符號斷點驗證,確實是走的這里,查看該方法的源碼實現(xiàn),主要有兩步操作通過
_dispatch_root_queues_init方法注冊回調(diào)通過do-while循環(huán)創(chuàng)建線程,使用
pthread_create方法
DISPATCH_NOINLINE
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{
int remaining = n;
int r = ENOSYS;
_dispatch_root_queues_init();//重點
...
//do-while循環(huán)創(chuàng)建線程
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--remaining);
...
}
_dispatch_root_queues_init
- 進入
_dispatch_root_queues_init源碼實現(xiàn),發(fā)現(xiàn)是一個dispatch_once_f單例(請查看后續(xù)單例的底層分析們,這里不作說明),其中傳入的func是_dispatch_root_queues_init_once。
DISPATCH_ALWAYS_INLINE
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的源碼,其內(nèi)部不同事務(wù)的調(diào)用句柄都是_dispatch_worker_thread2image
其block回調(diào)執(zhí)行的調(diào)用路徑為:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release
這個路徑可以通過斷點,bt打印堆棧信息得出
說明
在這里需要說明一點的是,單例的block回調(diào)和異步函數(shù)的block回調(diào)是不同的
單例中,block回調(diào)中的
func是_dispatch_Block_invoke(block)而
異步函數(shù)中,block回調(diào)中的func是dispatch_call_block_and_release
總結(jié)
所以,綜上所述,異步函數(shù)的底層分析如下
【準(zhǔn)備工作】:首先,將異步任務(wù)拷貝并封裝,并設(shè)置回調(diào)函數(shù)
func【block回調(diào)】:底層通過
dx_push遞歸,會重定向到根隊列,然后通過pthread_creat創(chuàng)建線程,最后通過dx_invoke執(zhí)行block回調(diào)(注意dx_push和dx_invoke是成對的)
異步函數(shù)的底層分析流程如圖所示

同步函數(shù)
進入dispatch_sync源碼實現(xiàn),其底層的實現(xiàn)是通過柵欄函數(shù)實現(xiàn)的(柵欄函數(shù)的底層分析見后文)
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_f源碼 -
查看
_dispatch_sync_f_inline源碼,其中width = 1表示是串行隊列,其中有兩個重點:柵欄:
_dispatch_barrier_sync_f(可以通過后文的柵欄函數(shù)底層分析解釋),可以得出同步函數(shù)的底層實現(xiàn)其實是同步柵欄函數(shù)死鎖:
_dispatch_sync_f_slow,如果存在相互等待的情況,就會造成死鎖
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);//處理當(dāng)前信息
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//block執(zhí)行并釋放
}
_dispatch_sync_f_slow 死鎖
-
進入
_dispatch_sync_f_slow,當(dāng)前的主隊列是掛起、阻塞的
往一個隊列中 加入任務(wù),會push加入主隊列,進入_dispatch_trace_item_push
進入__DISPATCH_WAIT_FOR_QUEUE__,判斷dq是否為正在等待的隊列,然后給出一個狀態(tài)state,然后將dq的狀態(tài)和當(dāng)前任務(wù)依賴的隊列進行匹配
進入
_dq_state_drain_locked_by -> _dispatch_lock_is_locked_by源碼
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
//異或操作:相同為0,不同為1,如果相同,則為0,0 &任何數(shù)都為0
//即判斷 當(dāng)前要等待的任務(wù) 和 正在執(zhí)行的任務(wù)是否一樣,通俗的解釋就是 執(zhí)行和等待的是否在同一隊列
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
如果當(dāng)前等待的和正在執(zhí)行的是同一個隊列,即判斷線程ID是否相乘,如果相等,則會造成死鎖
同步函數(shù) + 并發(fā)隊列 順序執(zhí)行的原因
在_dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline源碼中,主要有三個步驟:
- 將任務(wù)壓入隊列:
_dispatch_thread_frame_push - 執(zhí)行任務(wù)的block回調(diào):
_dispatch_client_callout - 將任務(wù)出隊:
_dispatch_thread_frame_pop
從實現(xiàn)中可以看出,是先將任務(wù)push隊列中,然后執(zhí)行block回調(diào),在將任務(wù)pop,所以任務(wù)是順序執(zhí)行的。
總結(jié)
同步函數(shù)的底層實現(xiàn)如下:
同步函數(shù)的底層實現(xiàn)實際是同步柵欄函數(shù)同步函數(shù)中如果
當(dāng)前正在執(zhí)行的隊列和等待的是同一個隊列,形成相互等待的局面,則會造成死鎖
所以,綜上所述,同步函數(shù)的底層實現(xiàn)流程如圖所示
單例
在日常開發(fā)中,我們一般使用GCD的dispatch_once來創(chuàng)建單例,如下所示
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"單例應(yīng)用");
});
首先對于單例,我們需要了解兩點
-
【執(zhí)行一次的原因】單例的流程只執(zhí)行一次,底層是如何控制的,即為什么只能執(zhí)行一次? -
【block調(diào)用時機】單例的block是在什么時候進行調(diào)用的?
下面帶著以下兩點疑問,我們來針對單例的底層進行分析
- 進入
dispatch_once源碼實現(xiàn),底層是通過dispatch_once_f實現(xiàn)的參數(shù)1:
onceToken,它是一個靜態(tài)變量,由于不同位置定義的靜態(tài)變量是不同的,所以靜態(tài)變量具有唯一性參數(shù)2:
block回調(diào)
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
- 進入
dispatch_once_f源碼,其中的val是外界傳入的onceToken靜態(tài)變量,而func是_dispatch_Block_invoke(block),其中單例的底層主要分為以下幾步將
val,也就是靜態(tài)變量轉(zhuǎn)換為dispatch_once_gate_t類型的變量l-
通過
os_atomic_load獲取此時的任務(wù)的標(biāo)識符v如果
v等于DLOCK_ONCE_DONE,表示任務(wù)已經(jīng)執(zhí)行過了,直接return如果 任務(wù)執(zhí)行后,
加鎖失敗了,則走到_dispatch_once_mark_done_if_quiesced函數(shù),再次進行存儲,將標(biāo)識符置為DLOCK_ONCE_DONE反之,則通過
_dispatch_once_gate_tryenter嘗試進入任務(wù),即解鎖,然后執(zhí)行_dispatch_once_callout執(zhí)行block回調(diào)
如果此時有任務(wù)正在執(zhí)行,再次進來一個任務(wù)2,則通過
_dispatch_once_wait函數(shù)讓任務(wù)2進入無限次等待
DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);//load
if (likely(v == DLOCK_ONCE_DONE)) {//已經(jīng)執(zhí)行過了,直接返回
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) {//嘗試進入
return _dispatch_once_callout(l, ctxt, func);
}
return _dispatch_once_wait(l);//無限次等待
}
_dispatch_once_gate_tryenter 解鎖
查看其源碼,主要是通過底層os_atomic_cmpxchg方法進行對比,如果比較沒有問題,則進行加鎖,即任務(wù)的標(biāo)識符置為DLOCK_ONCE_UNLOCKED
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);//首先對比,然后進行改變
}
_dispatch_once_callout 回調(diào)
進入_dispatch_once_callout源碼,主要就兩步
_dispatch_client_callout:block回調(diào)執(zhí)行_dispatch_once_gate_broadcast:進行廣播
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
_dispatch_client_callout(ctxt, func);//block調(diào)用執(zhí)行
_dispatch_once_gate_broadcast(l);//進行廣播:告訴別人有了歸屬,不要找我了
- 進入
_dispatch_client_callout源碼,主要就是執(zhí)行block回調(diào),其中的f等于_dispatch_Block_invoke(block),即異步回調(diào)
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
- 進入
_dispatch_once_gate_broadcast -> _dispatch_once_mark_done源碼,主要就是給dgo->dgo_once一個值,然后將任務(wù)的標(biāo)識符為DLOCK_ONCE_DONE,即解鎖
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
//如果不相同,直接改為相同,然后上鎖 -- DLOCK_ONCE_DONE
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
總結(jié)
針對單例的底層實現(xiàn),主要說明如下:
【單例只執(zhí)行一次的原理】:GCD單例中,有兩個重要參數(shù),
onceToken和block,其中onceToken是靜態(tài)變量,具有唯一性,在底層被封裝成了dispatch_once_gate_t類型的變量l,l主要是用來獲取底層原子封裝性的關(guān)聯(lián),即變量v,通過v來查詢?nèi)蝿?wù)的狀態(tài),如果此時v等于DLOCK_ONCE_DONE,說明任務(wù)已經(jīng)處理過一次了,直接return【block調(diào)用時機】:如果此時任務(wù)沒有執(zhí)行過,則會在底層通過C++函數(shù)的比較,將
任務(wù)進行加鎖,即任務(wù)狀態(tài)置為DLOCK_ONCE_UNLOCK,目的是為了保證當(dāng)前任務(wù)執(zhí)行的唯一性,防止在其他地方有多次定義。加鎖之后進行block回調(diào)函數(shù)的執(zhí)行,執(zhí)行完成后,將當(dāng)前任務(wù)解鎖,將當(dāng)前的任務(wù)狀態(tài)置為DLOCK_ONCE_DONE,在下次進來時,就不會在執(zhí)行,會直接返回【多線程影響】:如果在當(dāng)前任務(wù)執(zhí)行期間,有其他任務(wù)進來,會進入無限次等待,原因是當(dāng)前任務(wù)已經(jīng)獲取了鎖,進行了加鎖,其他任務(wù)是無法獲取鎖的
單例的底層流程分析如下如所示
柵欄函數(shù)
GCD中常用的柵欄函數(shù),主要有兩種
同步柵欄函數(shù)dispatch_barrier_sync(在主線程中執(zhí)行):前面的任務(wù)執(zhí)行完畢才會來到這里,但是同步柵欄函數(shù)會堵塞線程,影響后面的任務(wù)執(zhí)行異步柵欄函數(shù)dispatch_barrier_async:前面的任務(wù)執(zhí)行完畢才會來到這里
柵欄函數(shù)最直接的作用就是 控制任務(wù)執(zhí)行順序,使同步執(zhí)行。
同時,柵欄函數(shù)需要注意一下幾點
- 柵欄函數(shù)
只能控制同一并發(fā)隊列
同步柵欄添加進入隊列的時候,當(dāng)前線程會被鎖死,直到同步柵欄之前的任務(wù)和同步柵欄任務(wù)本身執(zhí)行完畢時,當(dāng)前線程才會打開然后繼續(xù)執(zhí)行下一句代碼。
在使用柵欄函數(shù)時.使用自定義隊列才有意義,如果用的是串行隊列,這個柵欄函數(shù)的作用等同于一個同步函數(shù)的作用,沒有任何意義;如果系統(tǒng)提供的全局并發(fā)隊列,由于全局并發(fā)隊列不是只有你在使用,系統(tǒng)也有可能在使用,可能引起不必要的問題。
代碼調(diào)試
總共有4個任務(wù),其中前2個任務(wù)有依賴關(guān)系,即任務(wù)1執(zhí)行完,執(zhí)行任務(wù)2,此時可以使用柵欄函數(shù)
-
異步柵欄函數(shù) 不會阻塞主線程 ,異步
堵塞 的是隊列 同步柵欄函數(shù) 會
堵塞主線程,同步 堵塞 是當(dāng)前的線程
總結(jié)
異步柵欄函數(shù)阻塞的是隊列,而且必須是自定義的并發(fā)隊列,不影響主線程任務(wù)的執(zhí)行同步柵欄函數(shù)阻塞的是線程,且是主線程,會影響主線程其他任務(wù)的執(zhí)行
使用場景
柵欄函數(shù)除了用于任務(wù)有依賴關(guān)系時,同時還可以用于數(shù)據(jù)安全
像下面這樣操作,會崩潰
崩潰的原因是:數(shù)據(jù)在不斷的retain 和 release,在數(shù)據(jù)還沒有retain完畢時,已經(jīng)開始了release,相當(dāng)于加了一個空數(shù)據(jù),進行release
修改
- 通過加?xùn)艡诤瘮?shù)
- (void)use041{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i<100000; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_barrier_async(concurrentQueue, ^{
[array addObject:[NSString stringWithFormat:@"%d", i]];
});
});
}
}
- 使用互斥鎖
@synchronized (self) {}
- (void)use041{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i<100000; i++) {
dispatch_async(concurrentQueue, ^{
@synchronized (self) {
[array addObject:[NSString stringWithFormat:@"%d", i]];
};
});
}
}
注意
如果柵欄函數(shù)中使用
全局隊列, 運行會崩潰,原因是系統(tǒng)也在用全局并發(fā)隊列,使用柵欄同時會攔截系統(tǒng)的,所以會崩潰如果將
自定義并發(fā)隊列改為串行隊列,即serial ,串行隊列本身就是有序同步此時加?xùn)艡?,會浪費性能柵欄函數(shù)
只會阻塞一次
異步柵欄函數(shù) 底層分析
進入dispatch_barrier_async源碼實現(xiàn),其底層的實現(xiàn)與dispatch_async類似,這里就不再做分析了,有興趣的可以自行探索下
#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc_flags);
}
#endif
同步柵欄函數(shù) 底層分析
進入dispatch_barrier_sync源碼,實現(xiàn)如下
void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
_dispatch_barrier_sync_f_inline
進入_dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline源碼
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();//獲取線程的id,即線程的唯一標(biāo)識
...
//判斷線程狀態(tài),需不需要等待,是否回收
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//柵欄函數(shù)也會死鎖
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//沒有回收
DC_FLAG_BARRIER | dc_flags);
}
//驗證target是否存在,如果存在,加入柵欄函數(shù)的遞歸查找 是否等待
if (unlikely(dl->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));//執(zhí)行
}
源碼主要有分為以下幾部分
通過
_dispatch_tid_self獲取線程ID-
通過
_dispatch_queue_try_acquire_barrier_sync判斷線程狀態(tài)
進入_dispatch_queue_try_acquire_barrier_sync_and_suspend,在這里進行釋放
通過
_dispatch_sync_recurse遞歸查找柵欄函數(shù)的target-
通過
_dispatch_introspection_sync_begin對向前信息進行處理
通過_dispatch_lane_barrier_sync_invoke_and_complete執(zhí)行block并釋放
信號量
信號量的作用一般是用來使任務(wù)同步執(zhí)行,類似于互斥鎖,用戶可以根據(jù)需要控制GCD最大并發(fā)數(shù),一般是這樣使用的
//信號量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
下面我們來分析其底層原理
dispatch_semaphore_create 創(chuàng)建
該函數(shù)的底層實現(xiàn)如下,主要是初始化信號量,并設(shè)置GCD的最大并發(fā)數(shù),其最大并發(fā)數(shù)必須大于0
dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. Therefore it is bogus to
// initialize the semaphore with a negative value.
if (value < 0) {
return DISPATCH_BAD_INPUT;
}
dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s));
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_default_queue(false);
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
dsema->dsema_orig = value;
return dsema;
}
dispatch_semaphore_wait 加鎖
該函數(shù)的源碼實現(xiàn)如下,其主要作用是對信號量dsema通過os_atomic_dec2o進行了--操作,其內(nèi)部是執(zhí)行的C++的atomic_fetch_sub_explicit方法
如果value 大于等于0,表示操作無效,即
執(zhí)行成功如果value 等于
LONG_MIN,系統(tǒng)會拋出一個crash如果value 小于0,則進入
長等待
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// dsema_value 進行 -- 操作
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {//表示執(zhí)行操作無效,即執(zhí)行成功
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);//長等待
}
其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下
os_atomic_inc2o(p, f, m)
??
os_atomic_sub2o(p, f, 1, m)
??
_os_atomic_c11_op((p), (v), m, sub, -)
??
_os_atomic_c11_op((p), (v), m, add, +)
??
({ _os_atomic_basetypeof(p) _v = (v), _r = \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
memory_order_##m); (__typeof__(_r))(_r op _v); })
將具體的值代入為
os_atomic_dec2o(dsema, dsema_value, acquire);
os_atomic_sub2o(dsema, dsema_value, 1, m)
os_atomic_sub(dsema->dsema_value, 1, m)
_os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -)
_r = atomic_fetch_sub_explicit(dsema->dsema_value, 1),
等價于 dsema->dsema_value - 1
-
進入
_dispatch_semaphore_wait_slow的源碼實現(xiàn),當(dāng)value小于0時,根據(jù)等待事件timeout做出不同操作
dispatch_semaphore_signal 解鎖
該函數(shù)的源碼實現(xiàn)如下,其核心也是通過os_atomic_inc2o函數(shù)對value進行了++操作,os_atomic_inc2o內(nèi)部是通過C++的atomic_fetch_add_explicit
如果value 大于 0,表示操作無效,即
執(zhí)行成功如果value 等于0,則進入
長等待
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
//signal 對 value是 ++
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {//返回0,表示當(dāng)前的執(zhí)行操作無效,相當(dāng)于執(zhí)行成功
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);//進入長等待
}
其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下
os_atomic_inc2o(p, f, m)
??
os_atomic_add2o(p, f, 1, m)
??
os_atomic_add(&(p)->f, (v), m)
??
_os_atomic_c11_op((p), (v), m, add, +)
??
({ _os_atomic_basetypeof(p) _v = (v), _r = \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
memory_order_##m); (__typeof__(_r))(_r op _v); })
將具體的值代入為
os_atomic_inc2o(dsema, dsema_value, release);
os_atomic_add2o(dsema, dsema_value, 1, m)
os_atomic_add(&(dsema)->dsema_value, (1), m)
_os_atomic_c11_op((dsema->dsema_value), (1), m, add, +)
_r = atomic_fetch_add_explicit(dsema->dsema_value, 1),
等價于 dsema->dsema_value + 1
總結(jié)
dispatch_semaphore_create主要就是初始化限號量dispatch_semaphore_wait是對信號量的value進行--,即加鎖操作dispatch_semaphore_signal是對信號量的value進行++,即解鎖操作
所以,綜上所述,信號量相關(guān)函數(shù)的底層操作如圖所示
調(diào)度組
調(diào)度組的最直接作用是控制任務(wù)執(zhí)行順序,常見方式如下
dispatch_group_create 創(chuàng)建組
dispatch_group_async 進組任務(wù)
dispatch_group_notify 進組任務(wù)執(zhí)行完畢通知 dispatch_group_wait 進組任務(wù)執(zhí)行等待時間
//進組和出組一般是成對使用的
dispatch_group_enter 進組
dispatch_group_leave 出組
使用
假設(shè)目前有兩個任務(wù),需要等待這兩個任務(wù)都執(zhí)行完畢,才會更新UI,可以使用調(diào)度組
【修改一】如果將dispatch_group_notify移動到最前面,能否執(zhí)行?
能執(zhí)行,但是是只要有enter-leave成對匹配,notify就會執(zhí)行,不會等兩個組都執(zhí)行完。意思就是只要enter-leave成對就可以執(zhí)行
-
【修改二】再加一個enter,即
enter:wait 是 3:2,能否執(zhí)行notify?
不能,會一直等待,等一個leave,才會執(zhí)行notify
-
【修改三】如果是 enter:wait 是 2:3,能否執(zhí)行notify?
會崩潰,因為enter-leave不成對,崩潰在里面是因為async有延遲
dispatch_group_create 創(chuàng)建組
主要是創(chuàng)建group,并設(shè)置屬性,此時的group的value為0
- 進入
dispatch_group_create源碼
dispatch_group_t
dispatch_group_create(void)
{
return _dispatch_group_create_with_count(0);
}
- 進入
_dispatch_group_create_with_count源碼,其中是對group對象屬性賦值,并返回group對象,其中的n等于0
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
//創(chuàng)建group對象,類型為OS_dispatch_group
dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
sizeof(struct dispatch_group_s));
//group對象賦值
dg->do_next = DISPATCH_OBJECT_LISTLESS;
dg->do_targetq = _dispatch_get_default_queue(false);
if (n) {
os_atomic_store2o(dg, dg_bits,
(uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
}
return dg;
}
dispatch_group_enter 進組
進入dispatch_group_enter源碼,通過os_atomic_sub_orig2o對dg->dg.bits 作 --操作,對數(shù)值進行處理
void
dispatch_group_enter(dispatch_group_t dg)
{
// The value is decremented on a 32bits wide atomic so that the carry
// for the 0 -> -1 transition is not propagated to the upper 32bits.
uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,//原子遞減 0 -> -1
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
if (unlikely(old_value == 0)) {//如果old_value
_dispatch_retain(dg); // <rdar://problem/22318411>
}
if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {//到達臨界值,會報crash
DISPATCH_CLIENT_CRASH(old_bits,
"Too many nested calls to dispatch_group_enter()");
}
}
dispatch_group_leave 出組
- 進入
dispatch_group_leave源碼- -1 到 0,即
++操作 - 根據(jù)狀態(tài),do-while循環(huán),喚醒執(zhí)行block任務(wù)
- 如果0 + 1 = 1,enter-leave不平衡,即leave多次調(diào)用,會crash
- -1 到 0,即
void
dispatch_group_leave(dispatch_group_t dg)
{
// The value is incremented on a 64bits wide atomic so that the carry for
// the -1 -> 0 transition increments the generation atomically.
uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,//原子遞增 ++
DISPATCH_GROUP_VALUE_INTERVAL, release);
uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);
//根據(jù)狀態(tài),喚醒
if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
old_state += DISPATCH_GROUP_VALUE_INTERVAL;
do {
new_state = old_state;
if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
} else {
// If the group was entered again since the atomic_add above,
// we can't clear the waiters bit anymore as we don't know for
// which generation the waiters are for
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
}
if (old_state == new_state) break;
} while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
old_state, new_state, &old_state, relaxed)));
return _dispatch_group_wake(dg, old_state, true);//喚醒
}
//-1 -> 0, 0+1 -> 1,即多次leave,會報crash,簡單來說就是enter-leave不平衡
if (unlikely(old_value == 0)) {
DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
"Unbalanced call to dispatch_group_leave()");
}
}
- 進入
_dispatch_group_wake源碼,do-while 循環(huán)進行異步命中,調(diào)用_dispatch_continuation_async執(zhí)行
DISPATCH_NOINLINE
static void
_dispatch_group_wake(dispatch_group_t dg, uint64_t dg_state, bool needs_release)
{
uint16_t refs = needs_release ? 1 : 0; // <rdar://problem/22318411>
if (dg_state & DISPATCH_GROUP_HAS_NOTIFS) {
dispatch_continuation_t dc, next_dc, tail;
// Snapshot before anything is notified/woken <rdar://problem/8554546>
dc = os_mpsc_capture_snapshot(os_mpsc(dg, dg_notify), &tail);
do {
dispatch_queue_t dsn_queue = (dispatch_queue_t)dc->dc_data;
next_dc = os_mpsc_pop_snapshot_head(dc, tail, do_next);
_dispatch_continuation_async(dsn_queue, dc,
_dispatch_qos_from_pp(dc->dc_priority), dc->dc_flags);//block任務(wù)執(zhí)行
_dispatch_release(dsn_queue);
} while ((dc = next_dc));//do-while循環(huán),進行異步任務(wù)的命中
refs++;
}
if (dg_state & DISPATCH_GROUP_HAS_WAITERS) {
_dispatch_wake_by_address(&dg->dg_gen);//地址釋放
}
if (refs) _dispatch_release_n(dg, refs);//引用釋放
}
- 進入
_dispatch_continuation_async源碼
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);//跟蹤日志
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);//與dx_invoke一樣,都是宏
}
這步與異步函數(shù)的block回調(diào)執(zhí)行是一致的,這里不再作說明
dispatch_group_notify 通知
- 進入
dispatch_group_notify源碼,如果old_state等于0,就可以進行釋放了
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dsn)
{
uint64_t old_state, new_state;
dispatch_continuation_t prev;
dsn->dc_data = dq;
_dispatch_retain(dq);
//獲取dg底層的狀態(tài)標(biāo)識碼,通過os_atomic_store2o獲取的值,即從dg的狀態(tài)碼 轉(zhuǎn)成了 os底層的state
prev = os_mpsc_push_update_tail(os_mpsc(dg, dg_notify), dsn, do_next);
if (os_mpsc_push_was_empty(prev)) _dispatch_retain(dg);
os_mpsc_push_update_prev(os_mpsc(dg, dg_notify), prev, dsn, do_next);
if (os_mpsc_push_was_empty(prev)) {
os_atomic_rmw_loop2o(dg, dg_state, old_state, new_state, release, {
new_state = old_state | DISPATCH_GROUP_HAS_NOTIFS;
if ((uint32_t)old_state == 0) { //如果等于0,則可以進行釋放了
os_atomic_rmw_loop_give_up({
return _dispatch_group_wake(dg, new_state, false);//喚醒
});
}
});
}
}
除了leave可以通過_dispatch_group_wake喚醒,其中dispatch_group_notify也是可以喚醒的
- 其中
os_mpsc_push_update_tail是宏定義,用于獲取dg的狀態(tài)碼
#define os_mpsc_push_update_tail(Q, tail, _o_next) ({ \
os_mpsc_node_type(Q) _tl = (tail); \
os_atomic_store2o(_tl, _o_next, NULL, relaxed); \
os_atomic_xchg(_os_mpsc_tail Q, _tl, release); \
})
dispatch_group_async
- 進入
dispatch_group_async源碼,主要是包裝任務(wù)和異步處理任務(wù)
#ifdef __BLOCKS__
void
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_block_t db)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
dispatch_qos_t qos;
//任務(wù)包裝器
qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
//處理任務(wù)
_dispatch_continuation_group_async(dg, dq, dc, qos);
}
#endif
- 進入
_dispatch_continuation_group_async源碼,主要是封裝了dispatch_group_enter進組操作
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dc, dispatch_qos_t qos)
{
dispatch_group_enter(dg);//進組
dc->dc_data = dg;
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//異步操作
}
-
進入
_dispatch_continuation_async源碼,執(zhí)行常規(guī)的異步函數(shù)底層操作。既然有了enter,肯定有l(wèi)eave,我們猜測block執(zhí)行之后隱性的執(zhí)行l(wèi)eave,通過斷點調(diào)試,打印堆棧信息 搜索
_dispatch_client_callout的調(diào)用,在_dispatch_continuation_with_group_invoke中
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_with_group_invoke(dispatch_continuation_t dc)
{
struct dispatch_object_s *dou = dc->dc_data;
unsigned long type = dx_type(dou);
if (type == DISPATCH_GROUP_TYPE) {//如果是調(diào)度組類型
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);//block回調(diào)
_dispatch_trace_item_complete(dc);
dispatch_group_leave((dispatch_group_t)dou);//出組
} else {
DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
}
所以,完美的印證dispatch_group_async底層封裝的是enter-leave
總結(jié)
enter-leave只要成對就可以,不管遠近dispatch_group_enter在底層是通過C++函數(shù),對group的value進行--操作(即0 -> -1)dispatch_group_leave在底層是通過C++函數(shù),對group的value進行++操作(即-1 -> 0)dispatch_group_notify在底層主要是判斷group的state是否等于0,當(dāng)?shù)扔?時,就通知block任務(wù)的喚醒,可以通過
dispatch_group_leave,也可以通過dispatch_group_notifydispatch_group_async等同于enter - leave,其底層的實現(xiàn)就是enter-leave
所以綜上所述,調(diào)度組的底層分析流程如下圖所示
dispatch_source
簡述
dispatch_source是基礎(chǔ)數(shù)據(jù)類型,用于協(xié)調(diào)特定底層系統(tǒng)事件的處理。
dispatch_source替代了異步回調(diào)函數(shù),來處理系統(tǒng)相關(guān)的事件,當(dāng)配置一個dispatch時,你需要指定監(jiān)測的事件、dispatch queue、以及處理事件的代碼(block或函數(shù))。當(dāng)事件發(fā)生時,dispatch source會提交你的block或函數(shù)到指定的queue去執(zhí)行
使用 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用聯(lián)結(jié)的優(yōu)勢。
聯(lián)結(jié)的大致流程為:在任一線程上調(diào)用它的一個函數(shù)dispatch_source_merge_data后,會執(zhí)行Dispatch Source事先定義好的句柄(可以把句柄簡單理解為一個block),這個過程叫 Custom event ,用戶事件。是 dispatch source 支持處理的一種事件。
簡單來說:這種事件是由你調(diào)用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號。
句柄是一種指向指針的指針,它指向的就是一個類或者結(jié)構(gòu),它和系統(tǒng)有密切的關(guān)系,這當(dāng)中還有一個通用的句柄,就是HANDLE
- 實例句柄 HINSTANCE
- 位圖句柄 HBITMAP
- 設(shè)備表句柄 HDC
- 圖標(biāo)句柄 HICON
特點
其CPU負荷非常小,金陵不占用資源
聯(lián)結(jié)的優(yōu)勢
使用
- 創(chuàng)建dispatch源
dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
| 參數(shù) | 說明 |
|---|---|
| type | dispatch源可處理的事件 |
| handle | 可以理解為句柄、索引或id,假如要監(jiān)聽進程,需要傳入進程的ID |
| mask | 可以理解為描述,提供更詳細的描述,讓它知道具體要監(jiān)聽什么 |
| queue | 自定義源需要的一個隊列,用來處理所有的響應(yīng)句柄 |
Dispatch Source 種類
其中type的類型有以下幾種
| 種類 | 說明 |
|---|---|
| DISPATCH_SOURCE_TYPE_DATA_ADD | 自定義的事件,變量增加 |
| DISPATCH_SOURCE_TYPE_DATA_OR | 自定義的事件,變量OR |
| DISPATCH_SOURCE_TYPE_MACH_SEND | MACH端口發(fā)送 |
| DISPATCH_SOURCE_TYPE_MACH_RECV | MACH端口接收 |
| DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | 內(nèi)存壓力 (注:iOS8后可用) |
| DISPATCH_SOURCE_TYPE_PROC | 進程監(jiān)聽,如進程的退出、創(chuàng)建一個或更多的子線程、進程收到UNIX信號 |
| DISPATCH_SOURCE_TYPE_READ | IO操作,如對文件的操作、socket操作的讀響應(yīng) |
| DISPATCH_SOURCE_TYPE_SIGNAL | 接收到UNIX信號時響應(yīng) |
| DISPATCH_SOURCE_TYPE_TIMER | 定時器 |
| DISPATCH_SOURCE_TYPE_VNODE | 文件狀態(tài)監(jiān)聽,文件被刪除、移動、重命名 |
| DISPATCH_SOURCE_TYPE_WRITE | IO操作,如對文件的操作、socket操作的寫響應(yīng) |
注意:
DISPATCH_SOURCE_TYPE_DATA_ADD
當(dāng)同一時間,一個事件的的觸發(fā)頻率很高,那么Dispatch Source會將這些響應(yīng)以ADD的方式進行累積,然后等系統(tǒng)空閑時最終處理,如果觸發(fā)頻率比較零散,那么Dispatch Source會將這些事件分別響應(yīng)。DISPATCH_SOURCE_TYPE_DATA_OR和上面的一樣,是自定義的事件,但是它是以O(shè)R的方式進行累積
常用函數(shù)
//掛起隊列
dispatch_suspend(queue)
//分派源創(chuàng)建時默認處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復(fù)
dispatch_resume(source)
//向分派源發(fā)送事件,需要注意的是,不可以傳遞0值(事件不會被觸發(fā)),同樣也不可以傳遞負數(shù)。
dispatch_source_merge_data
//設(shè)置響應(yīng)分派源事件的block,在分派源指定的隊列上運行
dispatch_source_set_event_handler
//得到分派源的數(shù)據(jù)
dispatch_source_get_data
//得到dispatch源創(chuàng)建,即調(diào)用dispatch_source_create的第二個參數(shù)
uintptr_t dispatch_source_get_handle(dispatch_source_t source);
//得到dispatch源創(chuàng)建,即調(diào)用dispatch_source_create的第三個參數(shù)
unsigned long dispatch_source_get_mask(dispatch_source_t source);
////取消dispatch源的事件處理--即不再調(diào)用block。如果調(diào)用dispatch_suspend只是暫停dispatch源。
void dispatch_source_cancel(dispatch_source_t source);
//檢測是否dispatch源被取消,如果返回非0值則表明dispatch源已經(jīng)被取消
long dispatch_source_testcancel(dispatch_source_t source);
//dispatch源取消時調(diào)用的block,一般用于關(guān)閉文件或socket等,釋放相關(guān)資源
void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler);
//可用于設(shè)置dispatch源啟動時調(diào)用block,調(diào)用完成后即釋放這個block。也可在dispatch源運行當(dāng)中隨時調(diào)用這個函數(shù)。
void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler);
使用場景
經(jīng)常用于驗證碼倒計時,因為dispatch_source不依賴于Runloop,而是直接和底層內(nèi)核交互,準(zhǔn)確性更高。
- (void)use033{
//倒計時時間
__block int timeout = 3;
//創(chuàng)建隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//創(chuàng)建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
//設(shè)置1s觸發(fā)一次,0s的誤差
/*
- source 分派源
- start 數(shù)控制計時器第一次觸發(fā)的時刻。參數(shù)類型是 dispatch_time_t,這是一個opaque類型,我們不能直接操作它。我們得需要 dispatch_time 和 dispatch_walltime 函數(shù)來創(chuàng)建它們。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
- interval 間隔時間
- leeway 計時器觸發(fā)的精準(zhǔn)程度
*/
dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
//觸發(fā)的事件
dispatch_source_set_event_handler(timer, ^{
//倒計時結(jié)束,關(guān)閉
if (timeout <= 0) {
//取消dispatch源
dispatch_source_cancel(timer);
}else{
timeout--;
dispatch_async(dispatch_get_main_queue(), ^{
//更新主界面的操作
NSLog(@"倒計時 - %d", timeout);
});
}
});
//開始執(zhí)行dispatch源
dispatch_resume(timer);
}
使用GCD中的dispatch_source實現(xiàn) 自定義倒計時按鈕
實現(xiàn)思路只要是繼承自UIButton,然后通過GCD的dispatch_source 實現(xiàn)倒計時按鈕,以下是demo的下載地址




































