iOS 底層探索:多線(xiàn)程GCD底層探索(下)

iOS 底層探索: 學(xué)習(xí)大綱 OC篇

前言

準(zhǔn)備

一 是辕、單例 dispatch_once底層分析

創(chuàng)建單例:

static HwProtocolManager *manager = nil;
static dispatch_once_t onceToken;
+ (instancetype)manager
{
    dispatch_once(& onceToken, ^{
        NSLog(@"單例");
        manager = [[HwProtocolManager alloc] init];
    });
    return manager;
}

對(duì)于單例我們知道葛峻,單例的流程只會(huì)執(zhí)行一次,為什么只執(zhí)行一次呢?我們來(lái)研究它的底層:

  • 進(jìn)入dispatch_once源碼實(shí)現(xiàn),底層是通過(guò)dispatch_once_f實(shí)現(xiàn)的
    • 參數(shù)1:onceToken勿她,它是一個(gè)靜態(tài)變量,static修飾阵翎,由于不同位置定義的靜態(tài)變量是不同的逢并,所以靜態(tài)變量具有唯一性
    • 參數(shù)2:block回調(diào)
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}

進(jìn)入dispatch_once_f源碼分析如下

DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
//1.將val,也就是靜態(tài)變量轉(zhuǎn)換為dispatch_once_gate_t類(lèi)型的變量l
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
//2.通過(guò)os_atomic_load獲取此時(shí)的任務(wù)的標(biāo)識(shí)符v
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);//load
//如果v等于DLOCK_ONCE_DONE郭卫,表示任務(wù)已經(jīng)執(zhí)行過(guò)了砍聊,直接return
    if (likely(v == DLOCK_ONCE_DONE)) {//已經(jīng)執(zhí)行過(guò)了,直接返回
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
//3.如果 任務(wù)執(zhí)行后贰军,加鎖失敗了玻蝌,則走到_dispatch_once_mark_done_if_quiesced函數(shù),再次進(jìn)行存儲(chǔ)词疼,將標(biāo)識(shí)符置為DLOCK_ONCE_DONE
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
//4.反之俯树,則通過(guò)_dispatch_once_gate_tryenter嘗試進(jìn)入任務(wù),即解鎖贰盗,然后執(zhí)行_dispatch_once_callout執(zhí)行block回調(diào)
    if (_dispatch_once_gate_tryenter(l)) {//嘗試進(jìn)入
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);//無(wú)限次等待
}

_dispatch_once_gate_tryenter 解鎖

DISPATCH_ALWAYS_INLINE
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);//首先對(duì)比许饿,然后進(jìn)行改變
}

查看其源碼,主要是通過(guò)底層os_atomic_cmpxchg方法進(jìn)行對(duì)比舵盈,如果比較沒(méi)有問(wèn)題陋率,則進(jìn)行加鎖,即任務(wù)的標(biāo)識(shí)符置為DLOCK_ONCE_UNLOCKED

_dispatch_once_callout 回調(diào)

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_client_callout(ctxt, func);//block調(diào)用執(zhí)行
    _dispatch_once_gate_broadcast(l);//進(jìn)行廣播:告訴別人有了歸屬书释,不要找我了
  • 進(jìn)入_dispatch_once_callout源碼翘贮,主要就兩步

    • _dispatch_client_callout:block回調(diào)執(zhí)行

    • _dispatch_once_gate_broadcast:進(jìn)行廣播

進(jìn)入_dispatch_client_callout源碼,

#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);
    }
    @catch (...) {
        objc_terminate();
    }
}

主要就是執(zhí)行block回調(diào)爆惧,其中的f等于_dispatch_Block_invoke(block)狸页,即異步回調(diào)

進(jìn)入_dispatch_once_gate_broadcast -> _dispatch_once_mark_done源碼,

DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    //如果不相同,直接改為相同芍耘,然后上鎖 -- DLOCK_ONCE_DONE
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

主要就是給dgo->dgo_once一個(gè)值址遇,然后將任務(wù)的標(biāo)識(shí)符為DLOCK_ONCE_DONE,即解鎖

針對(duì)單例的底層實(shí)現(xiàn)斋竞,總結(jié)如下:

  • 【單例只執(zhí)行一次的原理】:GCD單例中倔约,有兩個(gè)重要參數(shù),onceToken 和 block坝初,其中onceToken是靜態(tài)變量浸剩,具有唯一性,在底層被封裝成了dispatch_once_gate_t類(lèi)型的變量l鳄袍,l主要是用來(lái)獲取底層原子封裝性的關(guān)聯(lián)绢要,即變量v,通過(guò)v來(lái)查詢(xún)?nèi)蝿?wù)的狀態(tài)拗小,如果此時(shí)v等于DLOCK_ONCE_DONE重罪,說(shuō)明任務(wù)已經(jīng)處理過(guò)一次了,直接return

  • 【block調(diào)用時(shí)機(jī)】:如果此時(shí)任務(wù)沒(méi)有執(zhí)行過(guò)哀九,則會(huì)在底層通過(guò)C++函數(shù)的比較剿配,將任務(wù)進(jìn)行加鎖,即任務(wù)狀態(tài)置為DLOCK_ONCE_UNLOCK阅束,目的是為了保證當(dāng)前任務(wù)執(zhí)行的唯一性呼胚,防止在其他地方有多次定義。加鎖之后進(jìn)行block回調(diào)函數(shù)的執(zhí)行围俘,執(zhí)行完成后砸讳,將當(dāng)前任務(wù)解鎖,將當(dāng)前的任務(wù)狀態(tài)置為DLOCK_ONCE_DONE界牡,在下次進(jìn)來(lái)時(shí)簿寂,就不會(huì)在執(zhí)行,會(huì)直接返回

  • 【多線(xiàn)程影響】:如果在當(dāng)前任務(wù)執(zhí)行期間宿亡,有其他任務(wù)進(jìn)來(lái)常遂,會(huì)進(jìn)入無(wú)限次等待,原因是當(dāng)前任務(wù)已經(jīng)獲取了鎖挽荠,進(jìn)行了加鎖克胳,其他任務(wù)是無(wú)法獲取鎖的

單例的底層流程分析如下如所示

二 、柵欄函數(shù)的拓展和底層源碼分析

iOS 底層探索:多線(xiàn)程GCD的使用 已經(jīng)初步介紹過(guò)柵欄函數(shù)的使用圈匆,在這里再次進(jìn)行拓展分析一波兒漠另。

以前有個(gè)面試題類(lèi)似如下

當(dāng)時(shí)問(wèn):這樣寫(xiě)對(duì)嗎?為什么跃赚?答案是不對(duì)的笆搓,會(huì)崩潰性湿,為什么?經(jīng)過(guò)我實(shí)際驗(yàn)證有兩種方式不讓它崩潰满败,當(dāng)然還有更多的方法肤频,暫不說(shuō)明。

我們先看第一種方式如下:

這種方法是給可變數(shù)組一個(gè)固定的容量算墨。
再看第二種方式如下:


這種方法是給異步線(xiàn)程一個(gè)柵欄函數(shù)控制線(xiàn)程宵荒。

首先我們分析為什么為什么會(huì)崩潰:
我們?cè)俦罎⒌亩褩V锌梢钥吹饺缦拢?br>


崩潰之前調(diào)用了-[__NSArrayM insertObject:atIndex:]這個(gè)函數(shù),我們?cè)賝bjc底層源碼中去查看如下:

- (id)insertObject:anObject at:(unsigned)index
{
    register id *this, *last, *prev;
    if (! anObject) return nil;
    if (index > numElements)
        return nil;
    if ((numElements + 1) > maxElements) {
    volatile id *tempDataPtr;
    /* we double the capacity, also a good size for malloc */
    // 這里在數(shù)組超過(guò)一定的空間之后就進(jìn)行了雙倍的擴(kuò)容
    maxElements += maxElements + 1;
    // 這里數(shù)組tempDataPtr 進(jìn)行了realloc操作  所以在多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)的時(shí)候就會(huì)出現(xiàn)問(wèn)題
    tempDataPtr = (id *) realloc (dataPtr, DATASIZE(maxElements));
    dataPtr = (id*)tempDataPtr;
    }
    this = dataPtr + numElements;
    prev = this - 1;
    last = dataPtr + index;
    while (this > last) 
    *this-- = *prev--;
    *last = anObject;
    numElements++;
    return self;
}

- (id)addObject:anObject
{
    return [self insertObject:anObject at:numElements];
    
}

這段就是可變數(shù)組添加數(shù)據(jù)時(shí)候底層實(shí)現(xiàn)净嘀,可以很清晰的看到报咳,當(dāng)數(shù)組的容量超過(guò)一定的maxElements的時(shí)候就會(huì)maxElements += maxElements + 1;,并且進(jìn)行realloc重新創(chuàng)建了一個(gè)新的數(shù)組的操作挖藏,在多線(xiàn)程的操作少孝,如果數(shù)組添加的元素太多就會(huì)出現(xiàn)給舊數(shù)組添加元素的時(shí)候,舊的數(shù)組其實(shí)已經(jīng)被替代的情況熬苍,這樣就出現(xiàn)了崩潰。

我們可以驗(yàn)證在數(shù)組元素比較小的情況如下:

可以看到并不會(huì)崩潰!!!

所以經(jīng)過(guò)分析袁翁,我們得出結(jié)論柴底,異步并發(fā)執(zhí)行addObject的時(shí)候會(huì)造成數(shù)組指針賦值錯(cuò)誤的崩潰情況,當(dāng)數(shù)組的容量maxElements固定之后就不會(huì)重新realloc 粱胜,就避免了同時(shí)訪(fǎng)問(wèn)數(shù)組失敗的問(wèn)題柄驻,但是我們的第二種不讓其崩潰的解決方法使用了柵欄函dispatch_barrier_async使線(xiàn)程安全。
那么柵欄函數(shù)dispatch_barrier_async是怎么做到的呢焙压?鸿脓?

異步柵欄函數(shù) 底層分析如下:
進(jìn)入dispatch_barrier_async源碼實(shí)現(xiàn)

#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc_flags);
}
#endif

看到就會(huì)發(fā)現(xiàn),我們上一篇分析的dispatch_async的底層實(shí)現(xiàn)涯曲,dispatch_async的本質(zhì)其實(shí)就是dispatch_barrier_async野哭,所以這里就不在往下分析了,可以查看上一篇的內(nèi)容。

GCD中常用的柵欄函數(shù)幻件,主要有兩種

同步柵欄函數(shù)dispatch_barrier_sync(在主線(xiàn)程中執(zhí)行):前面的任務(wù)執(zhí)行完畢才會(huì)來(lái)到這里拨黔,但是同步柵欄函數(shù)會(huì)堵塞線(xiàn)程,影響后面的任務(wù)執(zhí)行

異步柵欄函數(shù)dispatch_barrier_async:前面的任務(wù)執(zhí)行完畢才會(huì)來(lái)到這里

柵欄函數(shù)最直接的作用就是控制任務(wù)執(zhí)行順序绰沥,使同步執(zhí)行篱蝇。

同時(shí),柵欄函數(shù)需要注意一下幾點(diǎn)

  • 柵欄函數(shù)只能控制同一并發(fā)隊(duì)列
  • 同步柵欄添加進(jìn)入隊(duì)列的時(shí)候徽曲,當(dāng)前線(xiàn)程會(huì)被鎖死零截,直到同步柵欄之前的任務(wù)和同步柵欄任務(wù)本身執(zhí)行完畢時(shí),當(dāng)前線(xiàn)程才會(huì)打開(kāi)然后繼續(xù)執(zhí)行下一句代碼秃臣。
  • 在使用柵欄函數(shù)時(shí).使用自定義隊(duì)列才有意義,如果用的是串行隊(duì)列或者系統(tǒng)提供的全局并發(fā)隊(duì)列,這個(gè)柵欄函數(shù)的作用等同于一個(gè)同步函數(shù)的作用涧衙,沒(méi)有任何意義

所以我們可以使用異步柵欄函數(shù)dispatch_barrier_async 在上面的addObject 方法中給任務(wù)添加類(lèi)似依賴(lài)關(guān)系,使線(xiàn)程安全

我們分析一下同步柵欄dispatch_barrier_sync源碼

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

進(jìn)入_dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline源碼

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func, uintptr_t dc_flags)
{
    dispatch_tid tid = _dispatch_tid_self();//獲取線(xiàn)程的id绍撞,即線(xiàn)程的唯一標(biāo)識(shí)
    
    ...
    
    //判斷線(xiàn)程狀態(tài)正勒,需不需要等待,是否回收
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//柵欄函數(shù)也會(huì)死鎖
        return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//沒(méi)有回收
                DC_FLAG_BARRIER | dc_flags);
    }
    //驗(yàn)證target是否存在傻铣,如果存在章贞,加入柵欄函數(shù)的遞歸查找 是否等待
    if (unlikely(dl->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func,
                DC_FLAG_BARRIER | dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);
    _dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
            DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
                    dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));//執(zhí)行
}

源碼主要有分為以下幾部分

通過(guò)_dispatch_tid_self獲取線(xiàn)程ID

通過(guò)_dispatch_queue_try_acquire_barrier_sync判斷線(xiàn)程狀態(tài)

進(jìn)入_dispatch_queue_try_acquire_barrier_sync_and_suspend,在這里進(jìn)行釋放

通過(guò)_dispatch_sync_recurse遞歸查找柵欄函數(shù)的target

通過(guò)_dispatch_introspection_sync_begin對(duì)向前信息進(jìn)行處理

通過(guò)_dispatch_lane_barrier_sync_invoke_and_complete執(zhí)行block并釋放

這里可以查看上篇 dispatch_sync同步任務(wù)的源碼分析非洲。 這里就不多做說(shuō)明了鸭限。

三 、信號(hào)量 dispatch_semaphore_t 的分析

信號(hào)量的作用一般是用來(lái)使任務(wù)同步執(zhí)行两踏,類(lèi)似于互斥鎖败京,用戶(hù)可以根據(jù)需要控制GCD最大并發(fā)數(shù),一般是這樣使用的

//信號(hào)量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
 注意:這兩個(gè)要成對(duì)出現(xiàn)

下面我們來(lái)分析其底層實(shí)現(xiàn)流程

dispatch_semaphore_create 創(chuàng)建
  • 該函數(shù)的底層實(shí)現(xiàn)如下梦染,主要是初始化信號(hào)量赡麦,并設(shè)置GCD的最大并發(fā)數(shù),其最大并發(fā)數(shù)必須大于0
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.
    //翻譯:
    //如果內(nèi)部值為負(fù)帕识,則該值的絕對(duì)值為
    //等于等待線(xiàn)程的數(shù)量泛粹。因此它是虛假的
    //用一個(gè)負(fù)值初始化信號(hào)量。
    if (value < 0) {
        return DISPATCH_BAD_INPUT;
    }
//初始化信號(hào)量
    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;
}
dispatch_semaphore_wait 加鎖

該函數(shù)的源碼實(shí)現(xiàn)如下肮疗,其主要作用是對(duì)信號(hào)量dsema通過(guò)os_atomic_dec2o進(jìn)行了--操作晶姊,其內(nèi)部是執(zhí)行的C++的atomic_fetch_sub_explicit方法

  • 如果value 大于等于0,表示操作無(wú)效伪货,即執(zhí)行成功

  • 如果value 等于LONG_MIN们衙,系統(tǒng)會(huì)拋出一個(gè)crash

  • 如果value 小于0,則進(jìn)入長(zhǎng)等待

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // dsema_value 進(jìn)行 -- 操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {//表示執(zhí)行操作無(wú)效碱呼,即執(zhí)行成功
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);//長(zhǎng)等待
}

其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下

os_atomic_inc2o(p, f, m)  
os_atomic_sub2o(p, f, 1, m).    
_os_atomic_c11_op((p), (v), m, sub, -) 1, m)
_os_atomic_c11_op((p), (v), m, add, +).   

({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })

將具體的值代入為

os_atomic_dec2o(dsema, dsema_value, acquire);
os_atomic_sub2o(dsema, dsema_value, 1, m)
os_atomic_sub(dsema->dsema_value, 1, m)
_os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -)
_r = atomic_fetch_sub_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value - 1
  • 進(jìn)入_dispatch_semaphore_wait_slow的源碼實(shí)現(xiàn)蒙挑,當(dāng)value小于0時(shí),根據(jù)等待事件timeout做出不同操作
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
        dispatch_time_t timeout)
{
    long orig;

    _dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    switch (timeout) {
    default:
        if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
            break;
        }
        // Fall through and try to undo what the fast path did to
        // dsema->dsema_value
            //失敗并嘗試撤銷(xiāo)快速路徑所造成的損失
            // dsema - > dsema_value
    case DISPATCH_TIME_NOW:
        orig = dsema->dsema_value;
        while (orig < 0) {
            if (os_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,
                    &orig, relaxed)) {
                return _DSEMA4_TIMEOUT();
            }
        }
        // Another thread called semaphore_signal().
        // Fall through and drain the wakeup.
            
            //另一個(gè)線(xiàn)程稱(chēng)為semaphore_signal()巍举。
            //喚醒失敗
            
    case DISPATCH_TIME_FOREVER:
        _dispatch_sema4_wait(&dsema->dsema_sema);
        break;
    }
    return 0;
}
dispatch_semaphore_signal 解鎖

該函數(shù)的源碼實(shí)現(xiàn)如下脆荷,其核心也是通過(guò)os_atomic_inc2o函數(shù)對(duì)value進(jìn)行了++操作,os_atomic_inc2o內(nèi)部是通過(guò)C++atomic_fetch_add_explicit

如果value 大于 0懊悯,表示操作無(wú)效蜓谋,即執(zhí)行成功

如果value 等于0,則進(jìn)入長(zhǎng)等待

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    //signal 對(duì) value是 ++
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {//返回0炭分,表示當(dāng)前的執(zhí)行操作無(wú)效桃焕,相當(dāng)于執(zhí)行成功
        return 0;
    }
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);//進(jìn)入長(zhǎng)等待
}

其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下

os_atomic_inc2o(p, f, m) 
os_atomic_add2o(p, f, 1, m)
os_atomic_add(&(p)->f, (v), m)
_os_atomic_c11_op((p), (v), m, add, +)
({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })

將具體的值代入為

os_atomic_inc2o(dsema, dsema_value, release);
os_atomic_add2o(dsema, dsema_value, 1, m) 
os_atomic_add(&(dsema)->dsema_value, (1), m)
_os_atomic_c11_op((dsema->dsema_value), (1), m, add, +)
_r = atomic_fetch_add_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value + 1

總結(jié)

  • dispatch_semaphore_create主要就是初始化限號(hào)量

  • dispatch_semaphore_wait是對(duì)信號(hào)量的value進(jìn)行--,即加鎖操作

  • dispatch_semaphore_signal是對(duì)信號(hào)量的value進(jìn)行++捧毛,即解鎖操作

所以观堂,綜上所述让网,信號(hào)量相關(guān)函數(shù)的底層操作如圖所示

四 、調(diào)度組 dispatch_group_ 的分析

調(diào)度組的最直接作用是控制任務(wù)執(zhí)行順序,常見(jiàn)操作如下

dispatch_group_create 創(chuàng)建組 
dispatch_group_async 進(jìn)組任務(wù) 
dispatch_group_notify 進(jìn)組任務(wù)執(zhí)行完畢通知 
dispatch_group_wait  暫停當(dāng)前線(xiàn)程(阻塞當(dāng)前線(xiàn)程)师痕,等待指定的 group 中的任務(wù)執(zhí)行完成后溃睹,才會(huì)往下繼續(xù)執(zhí)行

//進(jìn)組和出組一般是`成對(duì)使用`的 
dispatch_group_enter標(biāo)志著一個(gè)任務(wù)追加到 group,執(zhí)行一次胰坟,相當(dāng)于group 中未執(zhí)行完畢任務(wù)數(shù)+1
dispatch_group_leave標(biāo)志著一個(gè)任務(wù)離開(kāi)了 group因篇,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)-1笔横。
dispatch_group_create 創(chuàng)建組

主要是創(chuàng)建group竞滓,并設(shè)置屬性,此時(shí)的groupvalue0

  • 進(jìn)入dispatch_group_create源碼
dispatch_group_t
dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}
  • 進(jìn)入_dispatch_group_create_with_count源碼吹缔,其中是對(duì)group對(duì)象屬性賦值商佑,并返回group對(duì)象,其中的n等于0
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
    //創(chuàng)建group對(duì)象,類(lèi)型為OS_dispatch_group
    dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
            sizeof(struct dispatch_group_s));
    //group對(duì)象賦值
    dg->do_next = DISPATCH_OBJECT_LISTLESS;
    dg->do_targetq = _dispatch_get_default_queue(false);
    if (n) {
        os_atomic_store2o(dg, dg_bits,
                (uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
        os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
    }
    return dg;
}
dispatch_group_enter 進(jìn)組

進(jìn)入dispatch_group_enter源碼厢塘,通過(guò)os_atomic_sub_orig2o對(duì)dg->dg.bits--操作茶没,對(duì)數(shù)值進(jìn)行處理

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,//原子遞減 0 -> -1
            DISPATCH_GROUP_VALUE_INTERVAL, acquire);
    uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {//如果old_value
        _dispatch_retain(dg); // <rdar://problem/22318411>
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {//到達(dá)臨界值,會(huì)報(bào)crash
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}
dispatch_group_leave 出組

進(jìn)入dispatch_group_leave源碼
-1 到 0晚碾,即++操作
根據(jù)狀態(tài)礁叔,do-while循環(huán),喚醒執(zhí)行block任務(wù)
如果0 + 1 = 1迄薄,enter-leave不平衡,即leave多次調(diào)用煮岁,會(huì)crash

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);
    //根據(jù)狀態(tài)讥蔽,喚醒
    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);//喚醒
    }
    //-1 -> 0, 0+1 -> 1,即多次leave画机,會(huì)報(bào)crash冶伞,簡(jiǎn)單來(lái)說(shuō)就是enter-leave不平衡
    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}
  • 進(jìn)入_dispatch_group_wake源碼,do-while循環(huán)進(jìn)行異步命中步氏,調(diào)用_dispatch_continuation_async執(zhí)行
DISPATCH_NOINLINE
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);//block任務(wù)執(zhí)行
            _dispatch_release(dsn_queue);
        } while ((dc = next_dc));//do-while循環(huán)响禽,進(jìn)行異步任務(wù)的命中

        refs++;
    }

    if (dg_state & DISPATCH_GROUP_HAS_WAITERS) {
        _dispatch_wake_by_address(&dg->dg_gen);//地址釋放
    }

    if (refs) _dispatch_release_n(dg, refs);//引用釋放
}
  • 進(jìn)入_dispatch_continuation_async源碼
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
        dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
    if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
        _dispatch_trace_item_push(dqu, dc);//跟蹤日志
    }
#else
    (void)dc_flags;
#endif
    return dx_push(dqu._dq, dc, qos);//與dx_invoke一樣,都是宏
}

這步與異步函數(shù)的block回調(diào)執(zhí)行是一致的荚醒,這里不再作說(shuō)明

dispatch_group_notify 通知

進(jìn)入dispatch_group_notify源碼芋类,如果old_state等于0,就可以進(jìn)行釋放了

DISPATCH_ALWAYS_INLINE
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);
    //獲取dg底層的狀態(tài)標(biāo)識(shí)碼界阁,通過(guò)os_atomic_store2o獲取的值侯繁,即從dg的狀態(tài)碼 轉(zhuǎn)成了 os底層的state
    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) { //如果等于0,則可以進(jìn)行釋放了
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);//喚醒
                });
            }
        });
    }
}

除了leave可以通過(guò)_dispatch_group_wake喚醒泡躯,其中dispatch_group_notify也是可以喚醒的

其中os_mpsc_push_update_tail是宏定義贮竟,用于獲取dg的狀態(tài)碼

#define os_mpsc_push_update_tail(Q, tail, _o_next)  ({ \
    os_mpsc_node_type(Q) _tl = (tail); \
    os_atomic_store2o(_tl, _o_next, NULL, relaxed); \
    os_atomic_xchg(_os_mpsc_tail Q, _tl, release); \
})
dispatch_group_async

進(jìn)入dispatch_group_async 源碼丽焊,主要是包裝任務(wù)異步處理任務(wù)

#ifdef __BLOCKS__
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;
    //任務(wù)包裝器
    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    //處理任務(wù)
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}
#endif

進(jìn)入_dispatch_continuation_group_async源碼,主要是封裝了dispatch_group_enter進(jìn)組操作

DISPATCH_ALWAYS_INLINE
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);//進(jìn)組
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//異步操作
}

dispatch_group_enter 要和dispatch_group_leave 成對(duì)出現(xiàn)咕别,所以我們看看dispatch_group_async在哪里調(diào)用leave操作,堆棧調(diào)試如下:

_dispatch_continuation_with_group_invoke

DISPATCH_ALWAYS_INLINE
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) {//如果是調(diào)度組類(lèi)型
        _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);//block回調(diào)
        _dispatch_trace_item_complete(dc);
        dispatch_group_leave((dispatch_group_t)dou);//出組
    } else {
        DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
    }

可以看出dispatch_group_async底層封裝的是enter-leave

調(diào)度組的底層分析流程如下圖所示:


五 技健、總結(jié)

至此GCD孝常,常用的API辽俗,底層流程分析已經(jīng)差不多分析镜撩,其實(shí)還有很多不清晰的地方帜慢,以后再慢慢研究吧隔披。
頭疼休息一會(huì)兒.....

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壮莹,一起剝皮案震驚了整個(gè)濱河市绕德,隨后出現(xiàn)的幾起案子殃姓,更是在濱河造成了極大的恐慌翔冀,老刑警劉巖导街,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纤子,居然都是意外死亡搬瑰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)控硼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泽论,“玉大人,你說(shuō)我怎么就攤上這事卡乾∫磴玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵幔妨,是天一觀(guān)的道長(zhǎng)鹦赎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)误堡,這世上最難降的妖魔是什么古话? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锁施,結(jié)果婚禮上陪踩,老公的妹妹穿的比我還像新娘。我一直安慰自己悉抵,他們只是感情好肩狂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著姥饰,像睡著了一般婚温。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媳否,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天栅螟,我揣著相機(jī)與錄音荆秦,去河邊找鬼。 笑死力图,一個(gè)胖子當(dāng)著我的面吹牛步绸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吃媒,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瓤介,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了赘那?” 一聲冷哼從身側(cè)響起刑桑,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎募舟,沒(méi)想到半個(gè)月后祠斧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拱礁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年琢锋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呢灶。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吴超,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸯乃,到底是詐尸還是另有隱情鲸阻,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布缨睡,位于F島的核電站赘娄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宏蛉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一性置、第九天 我趴在偏房一處隱蔽的房頂上張望拾并。 院中可真熱鬧,春花似錦鹏浅、人聲如沸嗅义。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)之碗。三九已至,卻和暖如春季希,著一層夾襖步出監(jiān)牢的瞬間褪那,已是汗流浹背幽纷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留博敬,地道東北人友浸。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像偏窝,于是被迫代替她去往敵國(guó)和親收恢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • iOS 底層探索: 學(xué)習(xí)大綱 OC篇[/p/9d73ee7aae64] 前言 這篇開(kāi)始探索多線(xiàn)程中最常用的GCD祭往,...
    歐德?tīng)栘己?/span>閱讀 342評(píng)論 0 2
  • iOS 底層探索: 學(xué)習(xí)大綱 OC篇[/p/9d73ee7aae64] 前言 上篇我們復(fù)習(xí)了常用的GCD之后伦意,我們...
    歐德?tīng)栘己?/span>閱讀 705評(píng)論 1 3
  • iOS - 多線(xiàn)程 系列文章 iOS - 多線(xiàn)程(一):初識(shí)iOS - 多線(xiàn)程(二):pthread、NSThre...
    師大小海騰閱讀 1,902評(píng)論 0 6
  • 久違的晴天硼补,家長(zhǎng)會(huì)驮肉。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了括勺。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)缆八。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,493評(píng)論 16 22
  • 創(chuàng)業(yè)是很多人的夢(mèng)想,多少人為了理想和不甘選擇了創(chuàng)業(yè)來(lái)實(shí)現(xiàn)自我價(jià)值疾捍,我就是其中一個(gè)奈辰。 創(chuàng)業(yè)后,我由女人變成了超人乱豆,什...
    亦寶寶閱讀 1,802評(píng)論 4 1