11--多線(xiàn)程探索08--GCD源碼之dispatch_once

概述

dispatch_once能保證任務(wù)只會(huì)被執(zhí)行一次,即使同時(shí)多線(xiàn)程調(diào)用也是線(xiàn)程安全的跋涣。常用于創(chuàng)建單例揩环、swizzeld method等功能。

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

源碼(老)

在10之后的源碼中文兑,隱藏了很多實(shí)現(xiàn)細(xì)節(jié),不利于解讀過(guò)程腺劣,可以先看一份早期的源碼:

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);
    }
}

構(gòu)造的鏈表绿贞,這個(gè)結(jié)構(gòu)體在最新的源碼中是找不到的

struct _dispatch_once_waiter_s {
  truevolatile struct _dispatch_once_waiter_s *volatile dow_next;
  true_dispatch_thread_semaphore_t dow_sema;
};

block執(zhí)行的源碼,這段代碼后面也會(huì)分析

_dispatch_client_callout(ctxt, func);

我們都知道dispatch_once的魅力在于它可以保證代碼塊只執(zhí)行一次橘原,且是線(xiàn)程安全的

image.png

場(chǎng)景一:第一個(gè)線(xiàn)程第一次進(jìn)入

  1. 剛進(jìn)來(lái)時(shí):dispatch_once_t==nil籍铁,if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))條件成立,進(jìn)入if流程趾断;
  2. 調(diào)用:_dispatch_client_callout拒名,執(zhí)行block;
  3. 調(diào)用:dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE, relaxed);芋酌,將vval的值更新成DISPATCH_ONCE_DONE表示任務(wù)已完成增显;
  4. 遍歷鏈表的節(jié)點(diǎn)并調(diào)用_dispatch_thread_semaphore_signal來(lái)喚醒等待中的信號(hào)量;

場(chǎng)景二:第二個(gè)線(xiàn)程進(jìn)入

在第一個(gè)線(xiàn)程結(jié)束之前脐帝,第二個(gè)線(xiàn)程就已經(jīng)進(jìn)來(lái)了同云,但因?yàn)?code>dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire)里面的流程都是原子操作,原子表示最小的操作腮恩,不可分割的操作梢杭,所以其他進(jìn)來(lái)的線(xiàn)程需要進(jìn)入else流程

  1. if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))條件不成立秸滴,進(jìn)入else流程武契;
  2. 獲取當(dāng)前線(xiàn)程信號(hào)量:dow.dow_sema = _dispatch_get_thread_semaphore();
  3. 開(kāi)始無(wú)限循環(huán):for (;;)
  4. 阻塞線(xiàn)程:_dispatch_thread_semaphore_wait(dow.dow_sema);
  5. 循環(huán)退出條件:if (tmp == DISPATCH_ONCE_DONE) { break; }咒唆,在場(chǎng)景一中有最后一步届垫,當(dāng)任務(wù)完成時(shí),會(huì)vval的值更新成DISPATCH_ONCE_DONE全释,然后遍歷鏈表中的所有節(jié)點(diǎn)装处,并并調(diào)用_dispatch_thread_semaphore_signal來(lái)喚醒等待中的信號(hào)量,這個(gè)時(shí)候浸船,循環(huán)就會(huì)退出妄迁。
  6. 更新當(dāng)前線(xiàn)程信號(hào)量:_dispatch_put_thread_semaphore(dow.dow_sema)

場(chǎng)景三:同一個(gè)線(xiàn)程第二次進(jìn)入

當(dāng)場(chǎng)景一完成之后,vval的值為DISPATCH_ONCE_DONE李命,這個(gè)時(shí)候if (dispatch_atomic_cmpxchg(vval, NULL, &dow, acquire))條件就不成立鲫寄,同樣會(huì)走到else流程谬哀。

  • 場(chǎng)景一已完成妓肢,走進(jìn)for循環(huán)者疤,然后立馬跳出循環(huán),更新線(xiàn)程信號(hào)量阔籽,啥都沒(méi)做流妻;
  • 場(chǎng)景一未完成,跟場(chǎng)景二的流程一致笆制,進(jìn)入for循環(huán)绅这,直到vval的值更新成DISPATCH_ONCE_DONE

源碼(新)

在新的源碼中看不到信號(hào)量的作用了在辆。新的源碼中用了更多的宏君躺,流程更加抽象。但主體思路變化不大开缎。

源碼實(shí)現(xiàn)

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    if (_dispatch_once_gate_tryenter(l)) {
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);
}
  1. 先校驗(yàn)once狀態(tài)是否為DLOCK_ONCE_DONE,如果是林螃,則直接返回奕删,啥也不做,否則進(jìn)入第二步疗认;
  2. 條件判斷:_dispatch_once_gate_tryenter(l)完残,其實(shí)內(nèi)部實(shí)現(xiàn)也是跟舊的源碼差別不大:os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED, (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
  3. 條件通過(guò)則調(diào)用:_dispatch_once_callout(l, ctxt, func);横漏,這個(gè)流程跟舊源碼的if流程一模一樣谨设。先調(diào)用block,然后廣播信號(hào)量狀態(tài)改變的消息缎浇;
  4. 其他條件不通過(guò)時(shí)扎拣,則進(jìn)入:_dispatch_once_wait(l);流程,這個(gè)流程和else流程一致;

源碼定義

為了更加清晰的認(rèn)識(shí)新的源碼二蓝,將宏定義展開(kāi)誉券、函數(shù)嵌套展開(kāi)之后看過(guò)程。整體的流程仍然是完全一致刊愚,只是將信號(hào)量改成了iOS10之后的unfair_lock踊跟。

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
    if (os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed)) {
         //_dispatch_once_callout(l, ctxt, func);
         func(ctxt);
         dispatch_lock value_self = _dispatch_lock_value_for_self();
         uintptr_t v;
         v = _dispatch_once_mark_done(l);
         if (likely((dispatch_lock)v == value_self)) return;
         _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
         return;
    }
    
    // _dispatch_once_wait
    dispatch_lock self = _dispatch_lock_value_for_self();
    uintptr_t old_v, new_v;
    uint32_t timeout = 1;

    for (;;) {
        os_atomic_rmw_loop(&dgo->dgo_once, old_v, new_v, relaxed, {
            if (likely(old_v == DLOCK_ONCE_DONE)) {
                os_atomic_rmw_loop_give_up(return);
            }
            new_v = old_v | (uintptr_t)DLOCK_WAITERS_BIT;
            if (new_v == old_v) os_atomic_rmw_loop_give_up(break);
        });
        if (unlikely(_dispatch_lock_is_locked_by((dispatch_lock)old_v, self))) {
            DISPATCH_CLIENT_CRASH(0, "trying to lock recursively");
        }
        _dispatch_unfair_lock_wait(lock, (dispatch_lock)new_v, 0,
                DLOCK_LOCK_NONE);
        (void)timeout;
    }
}

定義

dispatch_once_t

typedef intptr_t dispatch_once_t;
static dispatch_once_t _dispatch_build_pred;

dispatch_once_gate_s, *dispatch_once_gate_t

typedef struct dispatch_once_gate_s {
    union {
        dispatch_gate_s dgo_gate;
        uintptr_t dgo_once;
    };
} dispatch_once_gate_s, *dispatch_once_gate_t;

寬度 acquire = __mo_acquire = 2

uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    rename everything to the wide variants
    重命名為指定寬度的變量 只需要考慮兩位即可
    enum class memory_order : __memory_order_underlying_t {
      relaxed = __mo_relaxed,
      consume = __mo_consume,
      acquire = __mo_acquire,
      release = __mo_release,
      acq_rel = __mo_acq_rel,
      seq_cst = __mo_seq_cst
    };
    enum __legacy_memory_order {
        __mo_relaxed,
        __mo_consume,
        __mo_acquire,
        __mo_release,
        __mo_acq_rel,
        __mo_seq_cst
    };

宏定義

DLOCK_GATE_UNLOCKED
    #define DLOCK_GATE_UNLOCKED ((dispatch_lock)0)
        0
DLOCK_ONCE_UNLOCKED
    #define DLOCK_ONCE_UNLOCKED ((uintptr_t)0)
        0x00
        0b0000 0000
DLOCK_ONCE_DONE
    #define DLOCK_ONCE_DONE     (~(uintptr_t)0)
        0xff
        0b1111 1111
DLOCK_FAILED_TRYLOCK_BIT
    #define DLOCK_FAILED_TRYLOCK_BIT ((dispatch_lock)0x00000002)
        0x02
        0b0000 0010
DISPATCH_NOESCAPE
    #define DISPATCH_NOESCAPE __attribute__((__noescape__))
DISPATCH_EXPECT
    #define DISPATCH_EXPECT(x, v) __builtin_expect((x), (v))

函數(shù)定義

DISPATCH_ONCE_MAKE_GEN(gen)
    (((gen) << 2) + DLOCK_FAILED_TRYLOCK_BIT)
        左移兩位
        + 0b0000 0010
DISPATCH_ONCE_IS_GEN(gen)
    (((gen) & 3) == DLOCK_FAILED_TRYLOCK_BIT)
        & 0b0000 0011
        比較 0b0000 0010
_dispatch_once_gate_tryenter
    static inline bool
    _dispatch_once_gate_tryenter(dispatch_once_gate_t l)
    {
        return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
                (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
    }
        l->dgo_once == 0

atomic.h

os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
    比較+交換
    l->dgo_once 與 DLOCK_ONCE_UNLOCKED 比較
    將 _dispatch_lock_value_for_self() 賦值給 l->dgo_once

_dispatch_once_callout流程

_dispatch_client_callout(ctxt, func);
    return f(ctxt);
    調(diào)用block

_dispatch_once_gate_broadcast(l);

_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
    dispatch_lock value_self = _dispatch_lock_value_for_self();
    uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    v = _dispatch_once_mark_quiescing(l);
#else
    v = _dispatch_once_mark_done(l);
#endif
    if (likely((dispatch_lock)v == value_self)) return;
    _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}

最后兩位結(jié)果為:最后兩位0b10

static inline uintptr_t
_dispatch_once_mark_quiescing(dispatch_once_gate_t dgo)
{
    return os_atomic_xchg(&dgo->dgo_once, _dispatch_once_generation(), release);
}
    static inline uintptr_t
    _dispatch_once_generation(void)
    {
        uintptr_t value;
        value = *(volatile uintptr_t *)_COMM_PAGE_CPU_QUIESCENT_COUNTER;
        return (uintptr_t)DISPATCH_ONCE_MAKE_GEN(value);
    }

最后兩位結(jié)果為:0b11

static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

深入淺出 GCD 之 dispatch_once
iOS多線(xiàn)程:GCD源碼分析<三>dispatch_once
atomic_cmpxchg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸥诽,隨后出現(xiàn)的幾起案子商玫,更是在濱河造成了極大的恐慌,老刑警劉巖牡借,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拳昌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蓖捶,警方通過(guò)查閱死者的電腦和手機(jī)地回,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俊鱼,“玉大人刻像,你說(shuō)我怎么就攤上這事〔⑾校” “怎么了细睡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)帝火。 經(jīng)常有香客問(wèn)我溜徙,道長(zhǎng),這世上最難降的妖魔是什么犀填? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任蠢壹,我火速辦了婚禮,結(jié)果婚禮上九巡,老公的妹妹穿的比我還像新娘图贸。我一直安慰自己,他們只是感情好冕广,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布疏日。 她就那樣靜靜地躺著,像睡著了一般撒汉。 火紅的嫁衣襯著肌膚如雪沟优。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天睬辐,我揣著相機(jī)與錄音挠阁,去河邊找鬼宾肺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鹃唯,可吹牛的內(nèi)容都是我干的爱榕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坡慌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼黔酥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起洪橘,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跪者,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后熄求,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體渣玲,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年弟晚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忘衍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卿城,死狀恐怖枚钓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瑟押,我是刑警寧澤搀捷,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站多望,受9級(jí)特大地震影響嫩舟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怀偷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一家厌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椎工,春花似錦像街、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脓斩。三九已至木西,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間随静,已是汗流浹背八千。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工吗讶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恋捆。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓照皆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親沸停。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膜毁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容