你不知道的dispatch_once

- (void)viewDidLoad {
    [super viewDidLoad];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self viewDidLoad];
    });
}

這樣調(diào)用會(huì)發(fā)生什么?
主線程掛起,應(yīng)用程序沒有響應(yīng),Why
我們刨根問底一下,自然會(huì)想到,dispatch_once是怎樣實(shí)現(xiàn)的。

我們知道,dispatch_once的作用是讓block只執(zhí)行一次,并且支持多線程,那么當(dāng)多個(gè)線程同時(shí)調(diào)用dispatch_once函數(shù)會(huì)怎么樣呢?


Sequence Process1-6.png

我么看到,每次進(jìn)來一個(gè)線程,都會(huì)生成一個(gè)struct

struct _dispatch_once_waiter_s {
    volatile struct _dispatch_once_waiter_s *volatile dow_next;
    _dispatch_thread_semaphore_t dow_sema;
};

其中dow_sema是用來存儲(chǔ)當(dāng)前線程的信號量,通過如下方式

 dow.dow_sema = _dispatch_get_thread_semaphore();

dow_next則指向了下一個(gè)結(jié)構(gòu)體變量(下一個(gè)進(jìn)來的線程),當(dāng)然,鏈表的tail節(jié)點(diǎn),粗略的說也就是第一個(gè)dow結(jié)構(gòu)體的dow_sema和dow_next都為null。


Sequence Process1-7.png

當(dāng)block執(zhí)行完, 通過

dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE)

會(huì)將onceToken(vval)標(biāo)記成block執(zhí)行完成狀態(tài),同時(shí)返回鏈表的head節(jié)點(diǎn)(因?yàn)橹?,vval是用來當(dāng)做指向頭結(jié)點(diǎn)指針用的,因此只要* vval一下即可),onceToken即能當(dāng)標(biāo)記位又能當(dāng)指針,真是又當(dāng)?shù)之?dāng)媽啊。
每次釋放哨兵的信號量的時(shí)候,也就是之前被阻塞的線程可以繼續(xù)運(yùn)行。

dispatch_atomic_cmpxchg(vval, tmp, &dow)

原子操作,比較tmp和&dow是否相等。

有了這些儲(chǔ)備,我們解釋一下上面的現(xiàn)象,當(dāng)?shù)谝淮蝑ispatch_once執(zhí)行的時(shí)候,在執(zhí)行block的時(shí)候,會(huì)再次進(jìn)入dispatch_once函數(shù),并且生成一個(gè)dow(_dispatch_once_waiter_s)結(jié)構(gòu)體,而此時(shí)onceToken還沒有標(biāo)記為完成狀態(tài),并且新的dow結(jié)構(gòu)體和第一次的dow不相等,這個(gè)結(jié)構(gòu)體會(huì)獲取主線程的信號量,緊接著,執(zhí)行wait,然后應(yīng)用程序掛起。對應(yīng)部分代碼如下

            if (tmp == DISPATCH_ONCE_DONE) {
                break;
            }
            dispatch_atomic_store_barrier();
            // 14. 如果當(dāng)前dispatch_once執(zhí)行的block沒有結(jié)束,那么就將這些
            // 后續(xù)請求添加到鏈表當(dāng)中
            if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
                dow.dow_next = tmp;
                _dispatch_thread_semaphore_wait(dow.dow_sema);
            }

還有個(gè)有趣的問題
1.當(dāng)家可以試試直接給onceToken寫成~0l,看看會(huì)發(fā)生什么?

下面是dispatch_once的源碼

#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í)行期進(jìn)入的dispatch_once_t更改請求的鏈表
    struct _dispatch_once_waiter_s *tail, *tmp;

    // 5.局部變量,用于在遍歷鏈表過程中獲取每一個(gè)在鏈表上的更改請求的信號量
    _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);

        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. 如果這個(gè)時(shí)候tmp的next指針還沒更新完畢,等一會(huì)
            while (!tmp->dow_next) {
                _dispatch_hardware_pause();
            }

            // 11. 取出當(dāng)前的信號量,告訴等待者,我這次更改請求完成了,輪到下一個(gè)了
            sema = tmp->dow_sema;
            tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
            _dispatch_thread_semaphore_signal(sema);
        }
    } else {
        // 12. 非首次請求,進(jìn)入這塊邏輯塊
        dow.dow_sema = _dispatch_get_thread_semaphore();
        for (;;) {
            // 13. 遍歷每一個(gè)后續(xù)請求,如果狀態(tài)已經(jīng)是Done,直接進(jìn)行下一個(gè)
            // 同時(shí)該狀態(tài)檢測還用于避免在后續(xù)wait之前,信號量已經(jīng)發(fā)出(signal)造成
            // 的死鎖
            tmp = *vval;
            if (tmp == DISPATCH_ONCE_DONE) {
                break;
            }
            dispatch_atomic_store_barrier();
            // 14. 如果當(dāng)前dispatch_once執(zhí)行的block沒有結(jié)束,那么就將這些
            // 后續(xù)請求添加到鏈表當(dāng)中
            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);
    }
}

參考文章:
http://blog.csdn.net/fishmai/article/details/52047249

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容