GCD除了多線程的能力牺陶,我們常常還會利用柵欄嘶卧、信號量等功能實(shí)現(xiàn)一些特定需求,本文將通過對libdispatch-1173.60.1源碼的解讀探究他的實(shí)現(xiàn)原理忌愚。
dispatch_once
通常我們常用GCD的dispatch_once
創(chuàng)建單例筛婉,或者某些只執(zhí)行一次的代碼,例如:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
<#code to be executed once#>
});
那么在底層的原理是怎樣的呢剔猿?在dispatch_once
函數(shù)的底層视译,是通過dispatch_once_f
實(shí)現(xiàn),先來看看源碼
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);
if (likely(v == DLOCK_ONCE_DONE)) {
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);
}
這個(gè)方法中归敬,首先會將傳入的靜態(tài)變量強(qiáng)轉(zhuǎn)為dispatch_once_gate_t
類型酷含,然后通過os_atomic_load
方法拿到任務(wù)標(biāo)識v
鄙早,而通過任務(wù)標(biāo)識v
,代碼流程可能會走以下三個(gè)分支。
- 如果v等于
DLOCK_ONCE_DONE
椅亚,說明這個(gè)任務(wù)已經(jīng)執(zhí)行過了限番,直接return
- 通過
_dispatch_once_gate_tryenter
鎖住當(dāng)前任務(wù),如果_dispatch_once_gate_tryenter(l)
返回為true
呀舔,說明這個(gè)任務(wù)block沒有執(zhí)行過弥虐,這通過_dispatch_once_callout
執(zhí)行任務(wù)block并開鎖,同時(shí)在_dispatch_once_gate_tryenter(l)
中將這個(gè)任務(wù)的標(biāo)識符置為DLOCK_ONCE_DONE
- 如果進(jìn)入鎖失敗媚赖,說明當(dāng)前任務(wù)正在執(zhí)行被鎖住霜瘪,則通過
_dispatch_once_wait(l)
進(jìn)入等待。
dispatch_semaphore
我們常常使用GCD的信號量去實(shí)現(xiàn)一些同步的功能惧磺。
通常我們是這么使用信號量的颖对。
dispatch_semaphore_t semphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
NSLog(@"test");
dispatch_semaphore_signal(semphore);
這里有三個(gè)關(guān)鍵函數(shù):dispatch_semaphore_create
、dispatch_semaphore_wait
磨隘、dispatch_semaphore_signal
dispatch_semaphore_create
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;
}
這是信號變量的創(chuàng)建過程缤底,這里我們只需要主要,我們將傳入的value
傳給了dsema_value
成員琳拭。
dispatch_semaphore_wait
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
這里的代碼有幾個(gè)步驟训堆,首先將傳入的信號變量dsema
的dsema_value
進(jìn)行進(jìn)行-1操作,如果得到的值value
大于或等于0白嘁,則直接返回0表示成功坑鱼,否則進(jìn)入_dispatch_semaphore_wait_slow
進(jìn)入長等待。
dispatch_semaphore_signal
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
這里和dispatch_semaphore_wait
相反絮缅,首先將dsema
的dsema_value
進(jìn)行+1操作鲁沥,如果value
的值大于0,則返回0執(zhí)行接下來的操作耕魄,如果value的值等于LONG_MIN
画恰,則拋出Unbalanced call to dispatch_semaphore_signal()
的異常,如果value
小于等于0吸奴,則進(jìn)入長等待允扇。
dispatch_group
舉個(gè)例子,我們通常這么使用GCD的調(diào)度組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)完成");
});
或者
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)完成");
});
不管哪種方式,進(jìn)組和出組必須成對出現(xiàn),如果進(jìn)組后沒有出組刽沾,則dispatch_group_notify
的任務(wù)不會執(zhí)行,如果沒有進(jìn)組便出組糊治,則會導(dǎo)致崩潰。
關(guān)于調(diào)度組原理的探究罚舱,可以先看dispatch_group_enter
的源碼
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,
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
if (unlikely(old_value == 0)) {
_dispatch_retain(dg); // <rdar://problem/22318411>
}
if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
DISPATCH_CLIENT_CRASH(old_bits,
"Too many nested calls to dispatch_group_enter()");
}
}
其實(shí)不用怎么看代碼井辜,注釋已經(jīng)幫我解釋了绎谦,這里將調(diào)度組dg
的old_bits
進(jìn)行-1操作
再來看dispatch_group_leave
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);
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);
}
if (unlikely(old_value == 0)) {
DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
"Unbalanced call to dispatch_group_leave()");
}
}
也可以通過注釋看出,這里是與enter相反的操作粥脚,這里將group的標(biāo)記進(jìn)行了+1操作窃肠,如果old_value
等于-1,也就是調(diào)度組的enter和leave是成對的出現(xiàn)阿逃,那么就調(diào)用_dispatch_group_wake
進(jìn)行下一步操作铭拧。
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);
_dispatch_release(dsn_queue);
} while ((dc = next_dc));
refs++;
}
if (dg_state & DISPATCH_GROUP_HAS_WAITERS) {
_dispatch_wake_by_address(&dg->dg_gen);
}
if (refs) _dispatch_release_n(dg, refs);
}
_dispatch_group_wake
內(nèi)部通過一個(gè)do-while循環(huán),最終調(diào)用的是_dispatch_continuation_async
去執(zhí)行目標(biāo)任務(wù)恃锉,而_dispatch_continuation_async
實(shí)際就是異步執(zhí)行任務(wù)的方法。
也就是說dispatch_group_leave
是可以喚醒調(diào)度組執(zhí)行最終任務(wù)的呕臂。
除了dispatch_group_leave
破托,另一個(gè)可以執(zhí)行目標(biāo)任務(wù)的方法是dispatch_group_notify
,再來看它的實(shí)現(xiàn)歧蒋。
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);
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) {
os_atomic_rmw_loop_give_up({
return _dispatch_group_wake(dg, new_state, false);
});
}
});
}
}
可以看到土砂,這里也是判斷state
是否為0,也就當(dāng)前調(diào)度組dg
進(jìn)出租是否成對谜洽,再通過_dispatch_group_wake
去執(zhí)行目標(biāo)任務(wù)萝映。
而調(diào)度組相關(guān)的有另一個(gè)方法dispatch_group_async
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;
qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
_dispatch_continuation_group_async(dg, dq, dc, qos);
}
接著看關(guān)鍵方法_dispatch_continuation_group_async
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);
}
顯而易見,這里也有進(jìn)組的操作阐虚,和我們顯式寫進(jìn)組出組實(shí)際是一樣的序臂。
那么他是何時(shí)出組的呢?我們可以通過斷點(diǎn)調(diào)試查看他出組的位置实束。
搜索_dispatch_client_callout
奥秆,可以發(fā)現(xiàn)在_dispatch_continuation_with_group_invoke
方法中有調(diào)用
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) {
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
_dispatch_trace_item_complete(dc);
dispatch_group_leave((dispatch_group_t)dou);
} else {
DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
}
}
這里可以發(fā)現(xiàn)dispatch_group_leave
這里調(diào)用了,所以dispatch_group_async
和dispatch_group_enter
咸灿、dispatch_group_leave
的用法本質(zhì)上其實(shí)是一樣的构订。
至此我們可以得出結(jié)論,調(diào)度的進(jìn)組和出組以成對的的避矢,當(dāng)dispatch_group_leave
或者dispatch_group_notify
調(diào)用時(shí)便會喚醒調(diào)度組執(zhí)行目標(biāo)任務(wù)悼瘾。