前言
dispatch_once能保證任務(wù)只會被執(zhí)行一次,即使同時多線程調(diào)用也是線程安全的。常用于創(chuàng)建單例、swizzeld method等功能。它的功能比較簡單,接下來看下使用方法和具體的原理。
使用篇
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//創(chuàng)建單例、method swizzled或其他任務(wù)
});
原理篇
//調(diào)用dispatch_once_f來處理
void dispatch_once(dispatch_once_t *val, dispatch_block_t block) {
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
dispatch_once封裝調(diào)用了dispatch_once_f函數(shù),其中通過_dispatch_Block_invoke來執(zhí)行block任務(wù),它的定義如下:
//invoke是指觸發(fā)block的具體實現(xiàn),感興趣的可以看一下Block_layout的結(jié)構(gòu)體
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
接著看一下具體的實現(xiàn)函數(shù)dispatch_once_f:
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);
}
}
由上面的代碼可知dispatch_once的流程圖大致如下:

首先看一下
dispatch_once中用的的原子性操作dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire),它的宏定義展開之后會將$dow賦值給vval,如果vval的初始值為NULL,返回YES,否則返回NO。
接著結(jié)合上面的流程圖來看下dispatch_once的代碼邏輯:
首次調(diào)用dispatch_once時,因為外部傳入的dispatch_once_t變量值為nil,故vval會為NULL,故if判斷成立。然后調(diào)用_dispatch_client_callout執(zhí)行block,然后在block執(zhí)行完成之后將vval的值更新成DISPATCH_ONCE_DONE表示任務(wù)已完成。最后遍歷鏈表的節(jié)點并調(diào)用_dispatch_thread_semaphore_signal來喚醒等待中的信號量;
當(dāng)其他線程同時也調(diào)用dispatch_once時,因為if判斷是原子性操作,故只有一個線程進入到if分支中,其他線程會進入else分支。在else分支中會判斷block是否已完成,如果已完成則跳出循環(huán);否則就是更新鏈表并調(diào)用_dispatch_thread_semaphore_wait阻塞線程,等待if分支中的block完成后再喚醒當(dāng)前等待的線程。
總結(jié)篇
dispatch_once用原子性操作block執(zhí)行完成標(biāo)記位,同時用信號量確保只有一個線程執(zhí)行block,等block執(zhí)行完再喚醒所有等待中的線程。
dispatch_once常被用于創(chuàng)建單例、swizzeld method等功能。