dispatch_once
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
其中第一個參數(shù)predicate,該參數(shù)是檢查后面第二個參數(shù)所代表的代碼塊是否被調(diào)用的謂詞。
第二個參數(shù)則是在整個應(yīng)用程序中只會被調(diào)用一次的代碼塊。dispach_once函數(shù)中的代碼塊只會被執(zhí)行一次,而且還是線程安全的。
使用dispatch_once創(chuàng)建單例
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
使用dispatch_once可以簡化代碼并且徹底保證線程安全,開發(fā)者根本無須擔心加鎖或同步。所有問題都有GCD在底層處理。由于每次調(diào)用時都必須使用完全相同的標記,所以標記要聲明成static。把該變量定義在static作用域中,可以保證編譯器在每次執(zhí)行shareInstance方法時都會復(fù)用這個變量,而不會創(chuàng)建新變量。此外,dispatch_once更高效。
單例 dispatch_once的淺析
once.h
#ifndef __DISPATCH_ONCE__
#define __DISPATCH_ONCE__
#ifndef __DISPATCH_INDIRECT__
#error "Please #include <dispatch/dispatch.h> instead of this file directly."
#include <dispatch/base.h> // for HeaderDoc
#endif
__BEGIN_DECLS
/*!
* @typedef dispatch_once_t
*
* @abstract
* A predicate for use with dispatch_once(). It must be initialized to zero.
* Note: static and global variables default to zero.
*/
typedef long dispatch_once_t;
/*!
* @function dispatch_once
*
* @abstract
* Execute a block once and only once.
*
* @param predicate
* A pointer to a dispatch_once_t that is used to test whether the block has
* completed or not.
*
* @param block
* The block to execute once.
*
* @discussion
* Always call dispatch_once() before using or testing any variables that are
* initialized by the block.
*/
#ifdef __BLOCKS__
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
//注意這個內(nèi)聯(lián)函數(shù)
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
_dispatch_once(dispatch_once_t *predicate, dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once(predicate, block);
}
}
#undef dispatch_once
#define dispatch_once _dispatch_once
#endif
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL1 DISPATCH_NONNULL3 DISPATCH_NOTHROW
void
dispatch_once_f(dispatch_once_t *predicate, void *context,
dispatch_function_t function);
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL1 DISPATCH_NONNULL3
DISPATCH_NOTHROW
void
_dispatch_once_f(dispatch_once_t *predicate, void *context,
dispatch_function_t function)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once_f(predicate, context, function);
}
}
#undef dispatch_once_f
#define dispatch_once_f _dispatch_once_f
__END_DECLS
#endif
once.c
#include "internal.h"
#undef dispatch_once
#undef dispatch_once_f
struct _dispatch_once_waiter_s
{
volatile struct _dispatch_once_waiter_s *volatile dow_next;
_dispatch_thread_semaphore_t dow_sema;
};
#define DISPATCH_ONCE_DONE ((struct _dispatch_once_waiter_s *)~0l)
#ifdef __BLOCKS__
// 1.應(yīng)用程序調(diào)用的入口
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
struct Block_basic *bb = (void *)block;
// 2. 內(nèi)部邏輯
dispatch_once_f(val, block, (void *)bb->Block_invoke);
}
#endif
DISPATCH_NOINLINE
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;
// 3. 類似于簡單的哨兵位
struct _dispatch_once_waiter_s dow = { NULL, 0 };
// 4. 在Dispatch_Once的block執(zhí)行期進入的dispatch_once_t更改請求的鏈表
struct _dispatch_once_waiter_s *tail, *tmp;
// 5.局部變量,用于在遍歷鏈表過程中獲取每一個在鏈表上的更改請求的信號量
_dispatch_thread_semaphore_t sema;
// 6. Compare and Swap(用于首次更改請求)
if (dispatch_atomic_cmpxchg(vval, NULL, &dow))
{
dispatch_atomic_acquire_barrier();
// 7.調(diào)用dispatch_once的block
_dispatch_client_callout(ctxt, func);
// The next barrier must be long and strong.
//
// The scenario: SMP systems with weakly ordered memory models
// and aggressive out-of-order instruction execution.
//
// The problem:
//
// The dispatch_once*() wrapper macro causes the callee's
// instruction stream to look like this (pseudo-RISC):
//
// load r5, pred-addr
// cmpi r5, -1
// beq 1f
// call dispatch_once*()
// 1f:
// load r6, data-addr
//
// May be re-ordered like so:
//
// load r6, data-addr
// load r5, pred-addr
// cmpi r5, -1
// beq 1f
// call dispatch_once*()
// 1f:
//
// Normally, a barrier on the read side is used to workaround
// the weakly ordered memory model. But barriers are expensive
// and we only need to synchronize once! After func(ctxt)
// completes, the predicate will be marked as "done" and the
// branch predictor will correctly skip the call to
// dispatch_once*().
//
// A far faster alternative solution: Defeat the speculative
// read-ahead of peer CPUs.
//
// Modern architectures will throw away speculative results
// once a branch mis-prediction occurs. Therefore, if we can
// ensure that the predicate is not marked as being complete
// until long after the last store by func(ctxt), then we have
// defeated the read-ahead of peer CPUs.
//
// In other words, the last "store" by func(ctxt) must complete
// and then N cycles must elapse before ~0l is stored to *val.
// The value of N is whatever is sufficient to defeat the
// read-ahead mechanism of peer CPUs.
//
// On some CPUs, the most fully synchronizing instruction might
// need to be issued.
//在寫入端,dispatch_once在執(zhí)行了block之后,會調(diào)用dispatch_atomic_maximally_synchronizing_barrier()
//宏函數(shù),在intel處理器上,這個函數(shù)編譯出的是cpuid指令。
dispatch_atomic_maximally_synchronizing_barrier();
//dispatch_atomic_release_barrier(); // assumed contained in above
// 8. 更改請求成為DISPATCH_ONCE_DONE(原子性的操作)
tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
tail = &dow;
// 9. 發(fā)現(xiàn)還有更改請求,繼續(xù)遍歷
while (tail != tmp)
{
// 10. 如果這個時候tmp的next指針還沒更新完畢,就等待一會,提示cpu減少額外處理,提升性能,節(jié)省電力。
while (!tmp->dow_next)
{
_dispatch_hardware_pause();
}
// 11. 取出當前的信號量,告訴等待者,這次更改請求完成了,輪到下一個了
sema = tmp->dow_sema;
tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
_dispatch_thread_semaphore_signal(sema);
}
} else
{
// 12. 非首次請求,進入此邏輯塊
dow.dow_sema = _dispatch_get_thread_semaphore();
// 13. 遍歷每一個后續(xù)請求,如果狀態(tài)已經(jīng)是Done,直接進行下一個
// 同時該狀態(tài)檢測還用于避免在后續(xù)wait之前,信號量已經(jīng)發(fā)出(signal)造成
// 的死鎖
for (;;)
{
tmp = *vval;
if (tmp == DISPATCH_ONCE_DONE)
{
break;
}
dispatch_atomic_store_barrier();
// 14. 如果當前dispatch_once執(zhí)行的block沒有結(jié)束,那么就將這些
// 后續(xù)請求添加到鏈表當中
if (dispatch_atomic_cmpxchg(vval, tmp, &dow))
{
dow.dow_next = tmp;
_dispatch_thread_semaphore_wait(dow.dow_sema);
}
}
_dispatch_put_thread_semaphore(dow.dow_sema);
}
}
dispatch_once不是只執(zhí)行一次那么簡單。內(nèi)部還是很復(fù)雜的。onceToken在第一次執(zhí)行block之前,它的值由NULL變?yōu)橹赶虻谝粋€調(diào)用者的指針(&dow)。dispatch_once是可以接受多次請求的,內(nèi)部會構(gòu)造一個鏈表來維護之。如果在block完成之前,有其它的調(diào)用者進來,則會把這些調(diào)用者放到一個waiter鏈表中(在else分支中的代碼)。waiter鏈表中的每個調(diào)用者會等待一個信號量(dow.dow_sema)。在block執(zhí)行完了后,除了將onceToken置為DISPATCH_ONCE_DONE外,還會去遍歷waiter鏈中的所有waiter,拋出相應(yīng)的信號量,以告知waiter們調(diào)用已經(jīng)結(jié)束了。
dispatch_once大致的過程
線程A執(zhí)行block時,其它線程都需要等待。
線程A執(zhí)行完block應(yīng)該立即標記任務(wù)為完成狀態(tài),然后遍歷信號量鏈來喚醒所有等待線程。
線程A遍歷信號量鏈來signal時,任何其他新進入函數(shù)的線程都應(yīng)該直接返回而無需等待。
線程A遍歷信號量鏈來signal時,若有其它等待線程B仍在更新或試圖更新信號量鏈表,應(yīng)該保證線程B能正確完成其任務(wù):a.直接返回 b.等待在信號量上并很快又被喚醒。
線程B構(gòu)造信號量時,應(yīng)該考慮線程A隨時可能改變狀態(tài)(等待、完成、遍歷信號量鏈表)。
線程B構(gòu)造信號量時,應(yīng)該考慮到另一個線程C也可能正在更新或試圖更新信號量鏈,應(yīng)該保證B、C都能正常完成其任務(wù):a.增加鏈節(jié)并等待在信號量上 b.發(fā)現(xiàn)線程A已經(jīng)標記“完成”然后直接銷毀信號量并退出函數(shù)。
要點
經(jīng)常需要編寫“只需要執(zhí)行一次的線程安全代碼”。通過GCD所提供的dispatch_once函數(shù),很容易就能實現(xiàn)此功能。
標記應(yīng)該聲明在static或global作用域中,這樣的話,在把只需執(zhí)行一次的塊傳給dispatch_once函數(shù)時,傳進去的標記也是相同的。