概述
dispatch_once能保證任務(wù)只會(huì)被執(zhí)行一次,即使同時(shí)多線(xiàn)程調(diào)用也是線(xiàn)程安全的跋涣。常用于創(chuàng)建單例揩环、swizzeld method等功能。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//創(chuàng)建單例贫母、method swizzled或其他任務(wù)
});
源碼(老)
在10之后的源碼中文兑,隱藏了很多實(shí)現(xiàn)細(xì)節(jié),不利于解讀過(guò)程腺劣,可以先看一份早期的源碼:
void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) {
struct _dispatch_once_waiter_s * volatile *vval =
(struct _dispatch_once_waiter_s**)val;
struct _dispatch_once_waiter_s dow = { NULL, 0 };
struct _dispatch_once_waiter_s *tail, *tmp;
_dispatch_thread_semaphore_t sema;
if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire)) {
_dispatch_client_callout(ctxt, func);
dispatch_atomic_maximally_synchronizing_barrier();
// above assumed to contain release barrier
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE, relaxed);
tail = &dow;
while (tail != tmp) {
while (!tmp->dow_next) {
dispatch_hardware_pause();
}
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else {
dow.dow_sema = _dispatch_get_thread_semaphore();
tmp = *vval;
for (;;) {
if (tmp == DISPATCH_ONCE_DONE) {
break;
}
if (dispatch_atomic_cmpxchgvw(vval, tmp, &dow, &tmp, release)) {
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
break;
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
構(gòu)造的鏈表绿贞,這個(gè)結(jié)構(gòu)體在最新的源碼中是找不到的
struct _dispatch_once_waiter_s {
truevolatile struct _dispatch_once_waiter_s *volatile dow_next;
true_dispatch_thread_semaphore_t dow_sema;
};
block執(zhí)行的源碼,這段代碼后面也會(huì)分析
_dispatch_client_callout(ctxt, func);
我們都知道dispatch_once
的魅力在于它可以保證代碼塊只執(zhí)行一次橘原,且是線(xiàn)程安全的
場(chǎng)景一:第一個(gè)線(xiàn)程第一次進(jìn)入
- 剛進(jìn)來(lái)時(shí):
dispatch_once_t==nil
籍铁,if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))
條件成立,進(jìn)入if流程趾断; - 調(diào)用:
_dispatch_client_callout
拒名,執(zhí)行block; - 調(diào)用:
dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE, relaxed);
芋酌,將vval的值更新成DISPATCH_ONCE_DONE
表示任務(wù)已完成增显; - 遍歷鏈表的節(jié)點(diǎn)并調(diào)用
_dispatch_thread_semaphore_signal
來(lái)喚醒等待中的信號(hào)量;
場(chǎng)景二:第二個(gè)線(xiàn)程進(jìn)入
在第一個(gè)線(xiàn)程結(jié)束之前脐帝,第二個(gè)線(xiàn)程就已經(jīng)進(jìn)來(lái)了同云,但因?yàn)?code>dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire)里面的流程都是原子操作,原子表示最小的操作腮恩,不可分割的操作梢杭,所以其他進(jìn)來(lái)的線(xiàn)程需要進(jìn)入else流程
。
-
if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))
條件不成立秸滴,進(jìn)入else流程
武契; - 獲取當(dāng)前線(xiàn)程信號(hào)量:
dow.dow_sema = _dispatch_get_thread_semaphore();
; - 開(kāi)始無(wú)限循環(huán):
for (;;)
; - 阻塞線(xiàn)程:
_dispatch_thread_semaphore_wait(dow.dow_sema);
- 循環(huán)退出條件:
if (tmp == DISPATCH_ONCE_DONE) { break; }
咒唆,在場(chǎng)景一中有最后一步届垫,當(dāng)任務(wù)完成時(shí),會(huì)vval的值更新成DISPATCH_ONCE_DONE
全释,然后遍歷鏈表中的所有節(jié)點(diǎn)装处,并并調(diào)用_dispatch_thread_semaphore_signal
來(lái)喚醒等待中的信號(hào)量,這個(gè)時(shí)候浸船,循環(huán)就會(huì)退出妄迁。 - 更新當(dāng)前線(xiàn)程信號(hào)量:
_dispatch_put_thread_semaphore(dow.dow_sema)
場(chǎng)景三:同一個(gè)線(xiàn)程第二次進(jìn)入
當(dāng)場(chǎng)景一完成之后,vval的值為DISPATCH_ONCE_DONE
李命,這個(gè)時(shí)候if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))
條件就不成立鲫寄,同樣會(huì)走到else流程
谬哀。
- 場(chǎng)景一已完成妓肢,走進(jìn)for循環(huán)者疤,然后立馬跳出循環(huán),更新線(xiàn)程信號(hào)量阔籽,啥都沒(méi)做流妻;
- 場(chǎng)景一未完成,跟場(chǎng)景二的流程一致笆制,進(jìn)入for循環(huán)绅这,直到vval的值更新成
DISPATCH_ONCE_DONE
。
源碼(新)
在新的源碼中看不到信號(hào)量的作用了在辆。新的源碼中用了更多的宏君躺,流程更加抽象。但主體思路變化不大开缎。
源碼實(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);
}
- 先校驗(yàn)once狀態(tài)是否為
DLOCK_ONCE_DONE
,如果是林螃,則直接返回奕删,啥也不做,否則進(jìn)入第二步疗认; - 條件判斷:
_dispatch_once_gate_tryenter(l)
完残,其實(shí)內(nèi)部實(shí)現(xiàn)也是跟舊的源碼差別不大:os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED, (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
; - 條件通過(guò)則調(diào)用:
_dispatch_once_callout(l, ctxt, func);
横漏,這個(gè)流程跟舊源碼的if流程
一模一樣谨设。先調(diào)用block,然后廣播信號(hào)量狀態(tài)改變的消息缎浇; - 其他條件不通過(guò)時(shí)扎拣,則進(jìn)入:
_dispatch_once_wait(l);
流程,這個(gè)流程和else流程
一致;
源碼定義
為了更加清晰的認(rèn)識(shí)新的源碼二蓝,將宏定義展開(kāi)誉券、函數(shù)嵌套展開(kāi)之后看過(guò)程。整體的流程仍然是完全一致刊愚,只是將信號(hào)量改成了iOS10之后的unfair_lock
踊跟。
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
if (os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed)) {
//_dispatch_once_callout(l, ctxt, func);
func(ctxt);
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
v = _dispatch_once_mark_done(l);
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
return;
}
// _dispatch_once_wait
dispatch_lock self = _dispatch_lock_value_for_self();
uintptr_t old_v, new_v;
uint32_t timeout = 1;
for (;;) {
os_atomic_rmw_loop(&dgo->dgo_once, old_v, new_v, relaxed, {
if (likely(old_v == DLOCK_ONCE_DONE)) {
os_atomic_rmw_loop_give_up(return);
}
new_v = old_v | (uintptr_t)DLOCK_WAITERS_BIT;
if (new_v == old_v) os_atomic_rmw_loop_give_up(break);
});
if (unlikely(_dispatch_lock_is_locked_by((dispatch_lock)old_v, self))) {
DISPATCH_CLIENT_CRASH(0, "trying to lock recursively");
}
_dispatch_unfair_lock_wait(lock, (dispatch_lock)new_v, 0,
DLOCK_LOCK_NONE);
(void)timeout;
}
}
定義
dispatch_once_t
typedef intptr_t dispatch_once_t;
static dispatch_once_t _dispatch_build_pred;
dispatch_once_gate_s, *dispatch_once_gate_t
typedef struct dispatch_once_gate_s {
union {
dispatch_gate_s dgo_gate;
uintptr_t dgo_once;
};
} dispatch_once_gate_s, *dispatch_once_gate_t;
寬度 acquire = __mo_acquire = 2
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
rename everything to the wide variants
重命名為指定寬度的變量 只需要考慮兩位即可
enum class memory_order : __memory_order_underlying_t {
relaxed = __mo_relaxed,
consume = __mo_consume,
acquire = __mo_acquire,
release = __mo_release,
acq_rel = __mo_acq_rel,
seq_cst = __mo_seq_cst
};
enum __legacy_memory_order {
__mo_relaxed,
__mo_consume,
__mo_acquire,
__mo_release,
__mo_acq_rel,
__mo_seq_cst
};
宏定義
DLOCK_GATE_UNLOCKED
#define DLOCK_GATE_UNLOCKED ((dispatch_lock)0)
0
DLOCK_ONCE_UNLOCKED
#define DLOCK_ONCE_UNLOCKED ((uintptr_t)0)
0x00
0b0000 0000
DLOCK_ONCE_DONE
#define DLOCK_ONCE_DONE (~(uintptr_t)0)
0xff
0b1111 1111
DLOCK_FAILED_TRYLOCK_BIT
#define DLOCK_FAILED_TRYLOCK_BIT ((dispatch_lock)0x00000002)
0x02
0b0000 0010
DISPATCH_NOESCAPE
#define DISPATCH_NOESCAPE __attribute__((__noescape__))
DISPATCH_EXPECT
#define DISPATCH_EXPECT(x, v) __builtin_expect((x), (v))
函數(shù)定義
DISPATCH_ONCE_MAKE_GEN(gen)
(((gen) << 2) + DLOCK_FAILED_TRYLOCK_BIT)
左移兩位
+ 0b0000 0010
DISPATCH_ONCE_IS_GEN(gen)
(((gen) & 3) == DLOCK_FAILED_TRYLOCK_BIT)
& 0b0000 0011
比較 0b0000 0010
_dispatch_once_gate_tryenter
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);
}
l->dgo_once == 0
atomic.h
os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
比較+交換
l->dgo_once 與 DLOCK_ONCE_UNLOCKED 比較
將 _dispatch_lock_value_for_self() 賦值給 l->dgo_once
_dispatch_once_callout流程
_dispatch_client_callout(ctxt, func);
return f(ctxt);
調(diào)用block
_dispatch_once_gate_broadcast(l);
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l);
#else
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
最后兩位結(jié)果為:最后兩位0b10
static inline uintptr_t
_dispatch_once_mark_quiescing(dispatch_once_gate_t dgo)
{
return os_atomic_xchg(&dgo->dgo_once, _dispatch_once_generation(), release);
}
static inline uintptr_t
_dispatch_once_generation(void)
{
uintptr_t value;
value = *(volatile uintptr_t *)_COMM_PAGE_CPU_QUIESCENT_COUNTER;
return (uintptr_t)DISPATCH_ONCE_MAKE_GEN(value);
}
最后兩位結(jié)果為:0b11
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
深入淺出 GCD 之 dispatch_once
iOS多線(xiàn)程:GCD源碼分析<三>dispatch_once
atomic_cmpxchg