GCD之dispatch_once

前言

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的流程圖大致如下:

圖片.png

首先看一下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等功能。

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

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

  • dispatch_once(dispatch_once_t *predicate, dispatch_block_...
    指尖彈灰閱讀 3,439評論 0 1
  • 很久前的總結(jié),今天貼出來。適合看了就用,很少講解,純粹用法。 目錄 Dispatch Queue dispatch...
    和女神經(jīng)常玩閱讀 786評論 0 3
  • 一:base.h 二:block.h 1. dispatch_block_flags:DISPATCH_BLOCK...
    小暖風(fēng)閱讀 2,787評論 0 0
  • 本文使用的源碼是libdispatch-187.10版本。至于為啥是這個版本,因為這一版的實現(xiàn)相對來說比較直觀、閱...
    Scott丶Wang閱讀 2,033評論 3 3
  • 【七絕】盼雨 文/舟亮 細(xì)雨憂憐老夢深,新雷尚未抖精神。 若須惜取三春事,已定今宵降甘霖。 (中華新韻,仄起首句入...
    舟亮閱讀 502評論 1 1

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