dispatch_once 簡單原理

概述

dispatch_once能保證任務只會被執(zhí)行一次,即使同時多線程調用也是線程安全的。常用于創(chuàng)建單例、
swizzeld method等功能。它的功能比較簡單,接下來看下使用方法和具體的原理。

使用篇

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //創(chuàng)建單例、method swizzled或其他任務
});

原理篇

//調用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封裝調用了dispatch_once_f函數(shù),其中通過_dispatch_Block_invoke來執(zhí)行block任務,它的定義如下:

//invoke是指觸發(fā)block的具體實現(xiàn),感興趣的可以看一下Block_layout的結構體
#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的流程圖大致如下:


image.png

首先看一下dispatch_once中用的的原子性操作dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire),它的宏定義展開之后會將$dow賦值給vval,如果vval的初始值為NULL,返回YES,否則返回NO。

接著結合上面的流程圖來看下dispatch_once的代碼邏輯:

首次調用dispatch_once時,因為外部傳入的dispatch_once_t變量值為nil,故vval會為NULL,故if判斷成立。然后調用_dispatch_client_callout執(zhí)行block,然后在block執(zhí)行完成之后將vval的值更新成DISPATCH_ONCE_DONE表示任務已完成。最后遍歷鏈表的節(jié)點并調用_dispatch_thread_semaphore_signal來喚醒等待中的信號量;

當其他線程同時也調用dispatch_once時,因為if判斷是原子性操作,故只有一個線程進入到if分支中,其他線程會進入else分支。在else分支中會判斷block是否已完成,如果已完成則跳出循環(huán);否則就是更新鏈表并調用_dispatch_thread_semaphore_wait阻塞線程,等待if分支中的block完成后再喚醒當前等待的線程。

總結:

dispatch_once用原子性操作block執(zhí)行完成標記位,同時用信號量確保只有一個線程執(zhí)行block,等block執(zhí)行完再喚醒所有等待中的線程。

dispatch_once常被用于創(chuàng)建單例、swizzeld method等功能。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容