GCD探究(三)-- 其他函數(shù)的探究

GCD除了多線程的能力牺陶,我們常常還會利用柵欄嘶卧、信號量等功能實(shí)現(xiàn)一些特定需求,本文將通過對libdispatch-1173.60.1源碼的解讀探究他的實(shí)現(xiàn)原理忌愚。

dispatch_once

通常我們常用GCD的dispatch_once創(chuàng)建單例筛婉,或者某些只執(zhí)行一次的代碼,例如:

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    <#code to be executed once#>
});

那么在底層的原理是怎樣的呢剔猿?在dispatch_once函數(shù)的底層视译,是通過dispatch_once_f實(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);
}

這個(gè)方法中归敬,首先會將傳入的靜態(tài)變量強(qiáng)轉(zhuǎn)為dispatch_once_gate_t類型酷含,然后通過os_atomic_load方法拿到任務(wù)標(biāo)識v鄙早,而通過任務(wù)標(biāo)識v,代碼流程可能會走以下三個(gè)分支。

  1. 如果v等于DLOCK_ONCE_DONE椅亚,說明這個(gè)任務(wù)已經(jīng)執(zhí)行過了限番,直接return
  2. 通過_dispatch_once_gate_tryenter鎖住當(dāng)前任務(wù),如果_dispatch_once_gate_tryenter(l)返回為true呀舔,說明這個(gè)任務(wù)block沒有執(zhí)行過弥虐,這通過_dispatch_once_callout執(zhí)行任務(wù)block并開鎖,同時(shí)在_dispatch_once_gate_tryenter(l)中將這個(gè)任務(wù)的標(biāo)識符置為DLOCK_ONCE_DONE
  3. 如果進(jìn)入鎖失敗媚赖,說明當(dāng)前任務(wù)正在執(zhí)行被鎖住霜瘪,則通過_dispatch_once_wait(l)進(jìn)入等待。

dispatch_semaphore

我們常常使用GCD的信號量去實(shí)現(xiàn)一些同步的功能惧磺。

通常我們是這么使用信號量的颖对。

dispatch_semaphore_t semphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
NSLog(@"test");
dispatch_semaphore_signal(semphore);

這里有三個(gè)關(guān)鍵函數(shù):dispatch_semaphore_createdispatch_semaphore_wait磨隘、dispatch_semaphore_signal

dispatch_semaphore_create

dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
    dispatch_semaphore_t dsema;

    // If the internal value is negative, then the absolute of the value is
    // equal to the number of waiting threads. Therefore it is bogus to
    // initialize the semaphore with a negative value.
    if (value < 0) {
        return DISPATCH_BAD_INPUT;
    }

    dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
            sizeof(struct dispatch_semaphore_s));
    dsema->do_next = DISPATCH_OBJECT_LISTLESS;
    dsema->do_targetq = _dispatch_get_default_queue(false);
    dsema->dsema_value = value;
    _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    dsema->dsema_orig = value;
    return dsema;
}

這是信號變量的創(chuàng)建過程缤底,這里我們只需要主要,我們將傳入的value傳給了dsema_value成員琳拭。

dispatch_semaphore_wait

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}

這里的代碼有幾個(gè)步驟训堆,首先將傳入的信號變量dsemadsema_value進(jìn)行進(jìn)行-1操作,如果得到的值value大于或等于0白嘁,則直接返回0表示成功坑鱼,否則進(jìn)入_dispatch_semaphore_wait_slow進(jìn)入長等待。

dispatch_semaphore_signal

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {
        return 0;
    }
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);
}

這里和dispatch_semaphore_wait相反絮缅,首先將dsemadsema_value進(jìn)行+1操作鲁沥,如果value的值大于0,則返回0執(zhí)行接下來的操作耕魄,如果value的值等于LONG_MIN画恰,則拋出Unbalanced call to dispatch_semaphore_signal()的異常,如果value小于等于0吸奴,則進(jìn)入長等待允扇。

dispatch_group

舉個(gè)例子,我們通常這么使用GCD的調(diào)度組

dispatch_group_t group = dispatch_group_create(); 
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任務(wù)1");
}); 
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任務(wù)2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"任務(wù)完成");
});

或者

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任務(wù)1");
    dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任務(wù)2");
    dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"任務(wù)完成");
});

不管哪種方式,進(jìn)組和出組必須成對出現(xiàn),如果進(jìn)組后沒有出組刽沾,則dispatch_group_notify的任務(wù)不會執(zhí)行,如果沒有進(jìn)組便出組糊治,則會導(dǎo)致崩潰。

關(guān)于調(diào)度組原理的探究罚舱,可以先看dispatch_group_enter的源碼

void
dispatch_group_enter(dispatch_group_t dg)
{
    // The value is decremented on a 32bits wide atomic so that the carry
    // for the 0 -> -1 transition is not propagated to the upper 32bits.
    uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
            DISPATCH_GROUP_VALUE_INTERVAL, acquire);
    uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {
        _dispatch_retain(dg); // <rdar://problem/22318411>
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}

其實(shí)不用怎么看代碼井辜,注釋已經(jīng)幫我解釋了绎谦,這里將調(diào)度組dgold_bits進(jìn)行-1操作

再來看dispatch_group_leave

void
dispatch_group_leave(dispatch_group_t dg)
{
    // The value is incremented on a 64bits wide atomic so that the carry for
    // the -1 -> 0 transition increments the generation atomically.
    uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
            DISPATCH_GROUP_VALUE_INTERVAL, release);
    uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);

    if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
        old_state += DISPATCH_GROUP_VALUE_INTERVAL;
        do {
            new_state = old_state;
            if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
                new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            } else {
                // If the group was entered again since the atomic_add above,
                // we can't clear the waiters bit anymore as we don't know for
                // which generation the waiters are for
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            }
            if (old_state == new_state) break;
        } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
                old_state, new_state, &old_state, relaxed)));
        return _dispatch_group_wake(dg, old_state, true);
    }

    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}

也可以通過注釋看出,這里是與enter相反的操作粥脚,這里將group的標(biāo)記進(jìn)行了+1操作窃肠,如果old_value等于-1,也就是調(diào)度組的enter和leave是成對的出現(xiàn)阿逃,那么就調(diào)用_dispatch_group_wake進(jìn)行下一步操作铭拧。

static void
_dispatch_group_wake(dispatch_group_t dg, uint64_t dg_state, bool needs_release)
{
    uint16_t refs = needs_release ? 1 : 0; // <rdar://problem/22318411>

    if (dg_state & DISPATCH_GROUP_HAS_NOTIFS) {
        dispatch_continuation_t dc, next_dc, tail;

        // Snapshot before anything is notified/woken <rdar://problem/8554546>
        dc = os_mpsc_capture_snapshot(os_mpsc(dg, dg_notify), &tail);
        do {
            dispatch_queue_t dsn_queue = (dispatch_queue_t)dc->dc_data;
            next_dc = os_mpsc_pop_snapshot_head(dc, tail, do_next);
            _dispatch_continuation_async(dsn_queue, dc,
                    _dispatch_qos_from_pp(dc->dc_priority), dc->dc_flags);
            _dispatch_release(dsn_queue);
        } while ((dc = next_dc));

        refs++;
    }

    if (dg_state & DISPATCH_GROUP_HAS_WAITERS) {
        _dispatch_wake_by_address(&dg->dg_gen);
    }

    if (refs) _dispatch_release_n(dg, refs);
}

_dispatch_group_wake內(nèi)部通過一個(gè)do-while循環(huán),最終調(diào)用的是_dispatch_continuation_async去執(zhí)行目標(biāo)任務(wù)恃锉,而_dispatch_continuation_async實(shí)際就是異步執(zhí)行任務(wù)的方法。

也就是說dispatch_group_leave是可以喚醒調(diào)度組執(zhí)行最終任務(wù)的呕臂。

除了dispatch_group_leave破托,另一個(gè)可以執(zhí)行目標(biāo)任務(wù)的方法是dispatch_group_notify,再來看它的實(shí)現(xiàn)歧蒋。

static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dsn)
{
    uint64_t old_state, new_state;
    dispatch_continuation_t prev;

    dsn->dc_data = dq;
    _dispatch_retain(dq);

    prev = os_mpsc_push_update_tail(os_mpsc(dg, dg_notify), dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) _dispatch_retain(dg);
    os_mpsc_push_update_prev(os_mpsc(dg, dg_notify), prev, dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) {
        os_atomic_rmw_loop2o(dg, dg_state, old_state, new_state, release, {
            new_state = old_state | DISPATCH_GROUP_HAS_NOTIFS;
            if ((uint32_t)old_state == 0) {
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);
                });
            }
        });
    }
}

可以看到土砂,這里也是判斷state是否為0,也就當(dāng)前調(diào)度組dg進(jìn)出租是否成對谜洽,再通過_dispatch_group_wake去執(zhí)行目標(biāo)任務(wù)萝映。

而調(diào)度組相關(guān)的有另一個(gè)方法dispatch_group_async

void
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_block_t db)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}

接著看關(guān)鍵方法_dispatch_continuation_group_async

static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dc, dispatch_qos_t qos)
{
    dispatch_group_enter(dg);
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

顯而易見,這里也有進(jìn)組的操作阐虚,和我們顯式寫進(jìn)組出組實(shí)際是一樣的序臂。

那么他是何時(shí)出組的呢?我們可以通過斷點(diǎn)調(diào)試查看他出組的位置实束。

搜索_dispatch_client_callout奥秆,可以發(fā)現(xiàn)在_dispatch_continuation_with_group_invoke方法中有調(diào)用

static inline void
_dispatch_continuation_with_group_invoke(dispatch_continuation_t dc)
{
    struct dispatch_object_s *dou = dc->dc_data;
    unsigned long type = dx_type(dou);
    if (type == DISPATCH_GROUP_TYPE) {
        _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
        _dispatch_trace_item_complete(dc);
        dispatch_group_leave((dispatch_group_t)dou);
    } else {
        DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
    }
}

這里可以發(fā)現(xiàn)dispatch_group_leave這里調(diào)用了,所以dispatch_group_asyncdispatch_group_enter咸灿、dispatch_group_leave的用法本質(zhì)上其實(shí)是一樣的构订。

至此我們可以得出結(jié)論,調(diào)度的進(jìn)組和出組以成對的的避矢,當(dāng)dispatch_group_leave或者dispatch_group_notify調(diào)用時(shí)便會喚醒調(diào)度組執(zhí)行目標(biāo)任務(wù)悼瘾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市审胸,隨后出現(xiàn)的幾起案子亥宿,更是在濱河造成了極大的恐慌,老刑警劉巖歹嘹,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箩绍,死亡現(xiàn)場離奇詭異,居然都是意外死亡尺上,警方通過查閱死者的電腦和手機(jī)材蛛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門圆到,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卑吭,你說我怎么就攤上這事芽淡。” “怎么了豆赏?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵挣菲,是天一觀的道長。 經(jīng)常有香客問我掷邦,道長白胀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任抚岗,我火速辦了婚禮或杠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宣蔚。我一直安慰自己向抢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布胚委。 她就那樣靜靜地躺著挟鸠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亩冬。 梳的紋絲不亂的頭發(fā)上艘希,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音鉴未,去河邊找鬼枢冤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铜秆,可吹牛的內(nèi)容都是我干的淹真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼连茧,長吁一口氣:“原來是場噩夢啊……” “哼核蘸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起啸驯,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤客扎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后罚斗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徙鱼,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袱吆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厌衙。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绞绒,靈堂內(nèi)的尸體忽然破棺而出婶希,到底是詐尸還是另有隱情,我是刑警寧澤蓬衡,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布喻杈,位于F島的核電站,受9級特大地震影響狰晚,放射性物質(zhì)發(fā)生泄漏筒饰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一家肯、第九天 我趴在偏房一處隱蔽的房頂上張望龄砰。 院中可真熱鬧,春花似錦讨衣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娘汞,卻和暖如春歹茶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背你弦。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工惊豺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人禽作。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓尸昧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旷偿。 傳聞我的和親對象是個(gè)殘疾皇子烹俗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

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