笔记-源码解析之dispatch_once、信号量、调度组

单例dispatch_once

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });

这串代码不用解释,相信大家都熟悉。现在前往源码解析

typedef long dispatch_once_t;

这里的once就是一个long类型,拿到它的指针类型传入到函数里。

跟着源码走,会进入到这里:

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

第一句代码把我们传进来的指针强转成dispatch_once_gate_t,进入这个会发现它是一个结构体,里面只有一个联合体。
再往下看的时候,可以先思考一个问题,单例是如何实现只创建一次的?带着这个问题,我们往下走,可以直接跳转到最后

    if (_dispatch_once_gate_tryenter(l)) {
        return _dispatch_once_callout(l, ctxt, func);
    }

从这句代码可以看出,这里是创建它。一步一步来,先看条件

_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
    // os 对象是否存储过
    // unlock
    return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}

说实话,懵逼,代码具体干嘛的不清楚,查资料后,可以理解成判断对象是否在os存储过。然后接着往下走

_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_client_callout(ctxt, func);
    _dispatch_once_gate_broadcast(l);
}

看过我之前文章的小伙伴,看到_dispatch_client_callout()这个应该很熟悉了,这里就是执行block里面的内容。创建完之后,就进行下面这个广播的函数

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

仔细看这段代码的意思,第一句拿到当前self对象,然后拿到一个v,仔细去看这个v到底是什么!!!

第一个_dispatch_once_mark_quiescing表示正在创建,这里标记了一个_dispatch_once_generation()

_dispatch_once_mark_quiescing(dispatch_once_gate_t dgo)
{
    return os_atomic_xchg(&dgo->dgo_once, _dispatch_once_generation(), release);
}

然后看第二个_dispatch_once_mark_done(),这里表示标记一个DLOCK_ONCE_DONE

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

此时此刻,是否还记得我们一开始跳过的方法里的代码,看下面代码:

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

如果标记了获取到的v等于DLOCK_ONCE_DONE,就直接返回;如果是下面的,我们在进入查看一下:

_dispatch_once_mark_done_if_quiesced(dispatch_once_gate_t dgo, uintptr_t gen)
{
    if (_dispatch_once_generation() - gen >= DISPATCH_ONCE_GEN_SAFE_DELTA) {
        /*
         * See explanation above, when the quiescing counter approach is taken
         * then this store needs only to be relaxed as it is used as a witness
         * that the required barriers have happened.
         */
        os_atomic_store(&dgo->dgo_once, DLOCK_ONCE_DONE, relaxed);
    }
}

不知道走到这里大家是否明白,单例是如何实现只创建一次的。

信号量dispatch_semaphore_t

    // 创建信号量对象 信号量 >= 0
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    // -1操作
    dispatch_wait(sem, DISPATCH_TIME_FOREVER);
    // +1 操作
    dispatch_semaphore_signal(sem);

上面三句代码,就是创建信号量的代码。 wait-1操作相当于阻塞操作,signal则是+1操作。

进入源码分析:

dispatch_semaphore_create(long value)
{
    dispatch_semaphore_t dsema;
    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;
}

第一步就声明了一个信号量对象,然后判断value,小于0就是不正确操作。后面就是创建对象,开辟空间。最主要的两步其实就是value的赋值。

接下来看一下dispatch_wait():

#define dispatch_wait(object, timeout) \
        _Generic((object), \
            dispatch_block_t:dispatch_block_wait, \
            dispatch_group_t:dispatch_group_wait, \
            dispatch_semaphore_t:dispatch_semaphore_wait \
        )((object),(timeout))

这里我们看的是信号量,所以走到dispatch_semaphore_wait方法里:

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

中间一个if条件判断,如果value >= 0,则直接返回一个0,返回0表示堵塞,允许进来的线程为0
所以我们看下os_atomic_dec2o()方法具体做了什么:

#define os_atomic_dec2o(p, f, m) \ os_atomic_sub2o(p, f, 1, m)             =======> 宏定义

#define os_atomic_sub2o(p, f, v, m) \ os_atomic_sub(&(p)->f, (v), m)        ========> 宏定义

#define os_atomic_sub(p, v, m) \ _os_atomic_c11_op((p), (v), m, sub, -)     ========> 宏定义

连续的几个宏定义,其实后面还有,不过看到这里已经够了,sub1-,不知道大家有没有看到这些敏感的字样,实际上这一步就是进行-1操作。

再看_dispatch_semaphore_wait_slow()方法,走到这步,说明value值是小于0的

_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
    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.
    case DISPATCH_TIME_FOREVER:
        _dispatch_sema4_wait(&dsema->dsema_sema);
        break;
    }
    return 0;
}

咔咔咔。。。其实这么多代码,就是告诉我们会一直的进行等待,它是对我们第二个参数设置的遍历。
我们这里设置的是forever,永久等待。注释也帮我们解答了,如果想要唤醒,那么就调用semaphore_signal()方法。

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

这里代码和前面的wait很相似,先看看os_atomic_inc2o()方法:

#define os_atomic_inc2o(p, f, m) \ os_atomic_add2o(p, f, 1, m)

#define os_atomic_add2o(p, f, v, m) \ os_atomic_add(&(p)->f, (v), m)

#define os_atomic_add(p, v, m) \ _os_atomic_c11_op((p), (v), m, add, +)

通过上面的代码,我相信聪明的你已经猜到了os_atomic_inc2o()的意义了,没错,就是+1操作。

后面的方法_dispatch_semaphore_signal_slow表示持续加一的状态,最后返回1,表示为非阻塞状态。

调度组dispatch_group_t

    dispatch_group_create()

    dispatch_group_enter()
    dispatch_group_leave() 

    dispatch_group_async(<#dispatch_group_t  _Nonnull group#>, <#dispatch_queue_t  _Nonnull queue#>, ^{
    
    });

    dispatch_group_notify(, , );

调度组我们围绕这几个方法来说,先看创建dispatch_group_create()

dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}

_dispatch_group_create_with_count(uint32_t n)
{
    dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
            sizeof(struct dispatch_group_s));
    dg->do_next = DISPATCH_OBJECT_LISTLESS;
    dg->do_targetq = _dispatch_get_default_queue(false);
    if (n) {
        os_atomic_store2o(dg, dg_bits,
                -n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
        os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // 
    }
    return dg;
}

可以看出就是创建了一个dispatch_group_t类型的对象,比较简单。

下面看dispatch_group_enter():

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); // 
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}

其实可以通过注释看到下面的操作是0 -> 1的操作,很明显是这个方法os_atomic_sub_orig2o

#define os_atomic_sub_orig2o(p, f, v, m) \ os_atomic_sub_orig(&(p)->f, (v), m)
#define os_atomic_sub_orig(p, v, m) \ _os_atomic_c11_op_orig((p), (v), m, sub, -)

然后判断,等于0直接返回,造成堵塞。

接着看dispatch_group_leave()

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)) {
        // 省略一些无关代码
        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()");
    }
}

同样的道理,第一步就是执行+1的操作,这里就不重复了。最后面有个判断,表示+1操作之后,还等于0,就会报错,这就要求dispatch_group_enter()dispatch_group_leave()要成对出现,不然会有问题。

主要看后面的方法_dispatch_group_wake

_dispatch_group_wake(dispatch_group_t dg, uint64_t dg_state, bool needs_release)
{
    uint16_t refs = needs_release ? 1 : 0; // 

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

        // Snapshot before anything is notified/woken 
        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);
}

走到这步就表明出里调度组,执行队列里的所有任务,里面有个do...while循环。

下面看另一个方法dispatch_group_async()

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

第一步_dispatch_continuation_init()操作是保存任务块
接着看_dispatch_continuation_group_async()

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

走到这里我们看到一个熟悉的方法dispatch_group_enter()上面刚刚说过的,而方法_dispatch_continuation_async()在上一篇文章里描述函数的部分也介绍过。不了解的小伙伴可以去看看

接着看方法_dispatch_group_notify()

_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dsn)
{
    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);
                });
            }
        });
    }
}

其实前面的一系列判断说什么都不重要,最重要的是我们看到了方法_dispatch_group_wake(),是不是又回到了dispatch_group_leave()方法
总结:
其实dispatch_group_async()_dispatch_group_notify()就是对dispatch_group_enter()dispatch_group_leave()的封装,不过个人感觉后面两者更加灵活。

上面描述如果有什么错误,还希望小伙伴们指出,一起学习一起交流!!!

你可能感兴趣的:(笔记-源码解析之dispatch_once、信号量、调度组)