iOS多线程——GCD底层探索下(栅栏与dispatch_once与dispatch_group与dispatch_semaphore_t)

栅栏函数的拓展和底层源码分析

栅栏函数基础

同步栅栏函数/异步栅栏函数

GCD中常用的栅栏函数,主要有两种

  • 同步栅栏函数dispatch_barrier_sync(在主线程中执行):

前面的任务执行完毕才会来到这里,但是同步栅栏函数会堵塞线程,影响后面的任务执行

  • 异步栅栏函数dispatch_barrier_async

前面的任务执行完毕才会来到这里

栅栏函数最直接的作用就是控制任务执行顺序,使同步执行。

栅栏函数栏队列

- (void)demo2 {
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue2, ^{
        sleep(2);
        NSLog(@"异步任务");
    });
    
    dispatch_barrier_async(queue2, ^{
        NSLog(@"栅栏任务");
    });
    
    NSLog(@"主线程任务");
}

打印结果

 主线程任务
 异步任务
 栅栏任务

异步栅栏函数阻塞的是队列,而且必须是自定义的并发队列,不影响主线程任务的执行

栅栏函数的同步/异步栏线程

- (void)demo2 {
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue2, ^{
        sleep(2);
        NSLog(@"异步任务");
    });
    
    dispatch_barrier_sync(queue2, ^{
        NSLog(@"栅栏任务");
    });
    
    NSLog(@"主线程任务");
}

打印结果

 异步任务
 栅栏任务
 主线程任务

dispatch_barrier_sync 它是同步,所以必须dispatch_barrier_sync^{ () }block 里面执行完,才执行后面

同步栅栏函数阻塞的是线程,且是主线程,会影响主线程其他任务的执行

栅栏函数必须是自定义并发队列

栅栏用于数据安全

崩溃代码

- (void)demo2 {
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    NSMutableArray *mArray = [NSMutableArray array];
    for (NSInteger i = 0; i < 5000; i++) {
        dispatch_async(queue2, ^{
            NSString *url = [NSString stringWithFormat:@"%ld.png",(long)i];
            [mArray addObject:url];
        });
    }

}

运行崩溃
请添加图片描述

崩溃分析

崩溃的堆栈中看到

崩溃之前调用了-[__NSArrayM insertObject:atIndex:]这个函数,我们再objc底层源码中去查看如下:

insertObject:底层源码

- (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 */
    // 这里在数组超过一定的空间之后就进行了双倍的扩容
    maxElements += maxElements + 1;
    // 这里数组tempDataPtr 进行了realloc操作  所以在多个线程同时访问的时候就会出现问题
    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];
    
}

这段就是可变数组添加数据时候底层实现,可以很清晰的看到,当数组的容量超过一定的maxElements的时候就会maxElements += maxElements + 1;,并且进行realloc重新创建了一个新的数组的操作,

在多线程的操作,如果数组添加的元素太多就会出现给旧数组添加元素的时候,旧的数组其实已经被替代的情况,这样就出现了崩溃。

数组元素比较小
请添加图片描述

可以看到并不会崩溃!!!

崩溃总结

异步并发执行addObject的时候会造成数组指针赋值错误的崩溃情况,当数组的容量maxElements固定之后就不会重新realloc ,就避免了同时访问数组失败的问题,

栅栏函dispatch_barrier_async使线程安全

互斥锁@synchronized 也是可以的

- (void)demo2 {
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    NSMutableArray *mArray = [NSMutableArray array];
    for (NSInteger i = 0; i < 10000; i++) {
        dispatch_async(queue2, ^{
            NSString *url = [NSString stringWithFormat:@"%ld.png",(long)i];
            
            dispatch_barrier_sync(queue2, ^{
                [mArray addObject:url];
                
            });
           
        });
    }

}

栅栏函数用全局队列还是崩溃

请添加图片描述

  • 如果栅栏函数中使用 全局队列, 运行会崩溃,原因是系统也在用全局并发队列,使用栅栏同时会拦截系统的,所以会崩溃
  • 如果将自定义并发队列改为串行队列,即serial ,串行队列本身就是有序同步 此时加栅栏,会浪费性能
  • 栅栏函数只会阻塞一次

dispatch_barrier_async源码

#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

看到就会发现,我们上一篇分析的dispatch_async的底层实现,dispatch_async的本质其实就是dispatch_barrier_async,所以这里就不在往下分析了,可以查看上一篇的内容

所以我们可以使用异步栅栏函数dispatch_barrier_async 在上面的addObject 方法中给任务添加类似依赖关系,使线程安全。

同步栅栏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);
}

进入_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();//获取线程的id,即线程的唯一标识
    
    ...
    
    //判断线程状态,需不需要等待,是否回收
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//栅栏函数也会死锁
        return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//没有回收
                DC_FLAG_BARRIER | dc_flags);
    }
    //验证target是否存在,如果存在,加入栅栏函数的递归查找 是否等待
    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)));//执行
}

1.通过_dispatch_tid_self获取线程ID
2.通过_dispatch_queue_try_acquire_barrier_sync判断线程状态
3.进入_dispatch_queue_try_acquire_barrier_sync_and_suspend,在这里进行释放
4. 通过_dispatch_sync_recurse递归查找栅栏函数的target
5.通过_dispatch_introspection_sync_begin对向前信息进行处理
6.通过_dispatch_lane_barrier_sync_invoke_and_complete执行block并释放

这里可以查看上篇 dispatch_sync同步任务的源码分析。 这里就不多做说明了。

栅栏函数的总结

  • 栅栏函数只能控制同一并发队列
  • 同步栅栏添加进入队列的时候,当前线程会被锁死,直到同步栅栏之前的任务和同步栅栏任务本身执行完毕时,当前线程才会打开然后继续执行下一句代码。
  • 在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,没有任何意义

单例 dispatch_once底层分析

单例的底层流程分析图

请添加图片描述

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

对于单例我们知道,单例的流程只会执行一次,为什么只执行一次呢?我们来研究它的底层

1.进入dispatch_once源码实现

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
	dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
  • 参数1:onceToken,它是一个静态变量,static修饰,由于不同位置定义的静态变量是不同的,所以静态变量具有唯一性
  • 参数2:block回调
    2. 进入进入dispatch_once_f源码分析
DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
//1.将val,也就是静态变量转换为dispatch_once_gate_t类型的变量l 它有唯一性
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
//2.通过os_atomic_load获取此时的任务的标识符v
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);//load
//如果v等于DLOCK_ONCE_DONE,表示任务已经执行过了,直接return
    if (likely(v == DLOCK_ONCE_DONE)) {//已经执行过了,直接返回
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
//3.如果 任务执行后,加锁失败了,则走到_dispatch_once_mark_done_if_quiesced函数,再次进行存储,将标识符置为DLOCK_ONCE_DONE
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
//4.反之,则通过_dispatch_once_gate_tryenter尝试进入任务,即解锁,然后执行_dispatch_once_callout执行block回调
    if (_dispatch_once_gate_tryenter(l)) {//尝试进入
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);//无限次等待
}
  • _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);//首先对比,然后进行改变
}

查看其源码,主要是通过底层os_atomic_cmpxchg方法进行对比,如果比较没有问题,则进行加锁,即任务的标识符置为DLOCK_ONCE_UNLOCKED

  • _dispatch_once_callout
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_client_callout(ctxt, func);//block调用执行
    _dispatch_once_gate_broadcast(l);//进行广播:告诉别人有了归属,不要找我了

进入_dispatch_once_callout源码,主要就两步

_dispatch_client_callout:block回调执行

_dispatch_once_gate_broadcast:进行广播

进入_dispatch_client_callout源码,

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

主要就是执行block回调,其中的f等于_dispatch_Block_invoke(block),即异步回调

进入_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一个值,然后将任务的标识符为DLOCK_ONCE_DONE,即解锁

单例的总结

【单例只执行一次的原理】:GCD单例中,有两个重要参数,onceToken 和
block,其中onceToken是静态变量,具有唯一性(每个单例的onceToken都不一样),在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return

【block调用时机】:如果此时任务没有执行过,则会在底层通过C++函数的比较,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行,会直接返回

【多线程影响】:如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的

调度组 dispatch_group底层分析

调度组相关函数的底层操作如图

请添加图片描述

调度组的最直接作用是控制任务执行顺序,常见操作如下

dispatch_group_create 创建组 
dispatch_group_async 进组任务 
dispatch_group_notify 进组任务执行完毕通知 
dispatch_group_wait  暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行

//进组和出组一般是`成对使用`的 
dispatch_group_enter标志着一个任务追加到 group,执行一次,相当于group 中未执行完毕任务数+1
dispatch_group_leave标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1

dispatch_group_create

主要是创建group,并设置属性,此时的group的value为0

1.进入dispatch_group_create源码

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

2.进入_dispatch_group_create_with_count源码,其中是对group对象属性赋值,并返回group对象,其中的n等于0

DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
    //创建group对象,类型为OS_dispatch_group
    dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
            sizeof(struct dispatch_group_s));
    //group对象赋值
    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 进组

进入dispatch_group_enter源码,通过os_atomic_sub_orig2o对dg->dg.bits 作–操作,对数值进行处理

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)) {//到达临界值,会报crash
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}

dispatch_group_leave 出组

进入dispatch_group_leave源码
-1 到 0,即++操作
根据状态,do-while循环,唤醒执行block任务
如果0 + 1 = 1,enter-leave不平衡,即leave多次调用,会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);
    //根据状态,唤醒
    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,会报crash,简单来说就是enter-leave不平衡
    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}

进入_dispatch_group_wake源码,do-while循环进行异步命中,调用_dispatch_continuation_async执行

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任务执行
            _dispatch_release(dsn_queue);
        } while ((dc = next_dc));//do-while循环,进行异步任务的命中

        refs++;
    }

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

    if (refs) _dispatch_release_n(dg, refs);//引用释放
}

进入_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一样,都是宏
}

这步与异步函数的block回调执行是一致的,这里不再作说明

dispatch_group_notify 通知

进入dispatch_group_notify源码,如果old_state等于0,就可以进行释放了

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底层的状态标识码,通过os_atomic_store2o获取的值,即从dg的状态码 转成了 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,则可以进行释放了
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);//唤醒
                });
            }
        });
    }
}

除了leave可以通过_dispatch_group_wake唤醒,其中dispatch_group_notify也是可以唤醒的

其中os_mpsc_push_update_tail是宏定义,用于获取dg的状态码

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

enter 和 leave 只要成对出现dispatch_group_notify 就会执行

dispatch_group_async = enter - leave

进入dispatch_group_async 源码,主要是包装任务和异步处理任务

#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;
    //任务包装器
    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    //处理任务
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}
#endif

进入_dispatch_continuation_group_async源码,主要是封装了dispatch_group_enter进组操作

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);//进组
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//异步操作
}

dispatch_group_enter 要和dispatch_group_leave 成对出现,所以我们看看dispatch_group_async在哪里调用leave操作,堆栈调试如下:

请添加图片描述

在_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) {//如果是调度组类型
        _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);//block回调
        _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

组调度总结

  • enter-leave只要成对就可以,不管远近
  • dispatch_group_enter在底层是通过C++函数,对group的value进行–操作(即0 -> -1)
  • dispatch_group_leave在底层是通过C++函数,对group的value进行++操作(即-1 -> 0)
  • dispatch_group_notify在底层主要是判断group的state是否等于0,当等于0时,就通知
  • block任务的唤醒,可以通过dispatch_group_leave,也可以通过dispatch_group_notify
  • dispatch_group_async 等同于enter - leave,其底层的实现就是enter-leave

信号量 dispatch_semaphore_t 底层分析

信号量相关函数的底层操作如图

请添加图片描述

信号量的作用一般是用来使任务同步执行,类似于互斥锁,用户可以根据需要控制GCD最大并发数,一般是这样使用的

//信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
 注意:这两个要成对出现

dispatch_semaphore_create 创建

该函数的底层实现如下,主要是初始化信号量,并设置GCD的最大并发数,其最大并发数必须大于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.
    //翻译:
    //如果内部值为负,则该值的绝对值为
    //等于等待线程的数量。因此它是虚假的
    //用一个负值初始化信号量。
    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;
}

dispatch_semaphore_wait 加锁

该函数的源码实现如下,其主要作用是对信号量dsema通过os_atomic_dec2o进行了–操作,其内部是执行的C++的atomic_fetch_sub_explicit方法

  • 如果value 大于等于0,表示操作无效,即执行成功
  • 如果value 等于LONG_MIN,系统会抛出一个crash
  • 如果value 小于0,则进入长等待
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // dsema_value 进行 -- 操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {//表示执行操作无效,即执行成功
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);//长等待
}

其中os_atomic_dec2o的宏定义转换如下

  • os_atomic_inc2o(p, f, m)

  • os_atomic_sub2o(p, f, 1, m).

  • _os_atomic_c11_op(§, (v), m, sub, -) 1, m)

  • _os_atomic_c11_op(§, (v), m, add, +).

  • ({ _os_atomic_basetypeof§ _v = (v), _r = \

atomic_fetch_##o##_explicit(_os_atomic_c11_atomic§, 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), 等价于 dsema->dsema_value - 1

进入_dispatch_semaphore_wait_slow的源码实现,当value小于0时,根据等待事件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
            //失败并尝试撤销快速路径所造成的损失
            // 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.
            
            //另一个线程称为semaphore_signal()。
            //唤醒失败
            
    case DISPATCH_TIME_FOREVER:
        _dispatch_sema4_wait(&dsema->dsema_sema);
        break;
    }
    return 0;
}

dispatch_semaphore_signal 解锁

该函数的源码实现如下,其核心也是通过os_atomic_inc2o函数对value进行了++操作,os_atomic_inc2o内部是通过C++的atomic_fetch_add_explicit

如果value 大于 0,表示操作无效,即执行成功

如果value 等于0,则进入长等待

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    //signal 对 value是 ++
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {//返回0,表示当前的执行操作无效,相当于执行成功
        return 0;
    }
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);//进入长等待
}

其中os_atomic_dec2o的宏定义转换如下

os_atomic_inc2o(p, f, m)
os_atomic_add2o(p, f, 1, m)
os_atomic_add(&§->f, (v), m)
_os_atomic_c11_op(§, (v), m, add, +)
({ _os_atomic_basetypeof§ _v = (v), _r = \

atomic_fetch_##o##_explicit(_os_atomic_c11_atomic§, 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), 等价于 dsema->dsema_value + 1

信号量的总结

  • dispatch_semaphore_create主要就是初始化限号量
  • dispatch_semaphore_wait是对信号量的value进行–,即加锁操作
  • dispatch_semaphore_signal是对信号量的value进行++,即解锁操作

dispatch_source

常用函数

//挂起队列
dispatch_suspend(queue)

//分派源创建时默认处于暂停状态,在分派源分派处理程序之前必须先恢复 dispatch_resume(source)

//向分派源发送事件,需要注意的是,不可以传递0值(事件不会被触发),同样也不可以传递负数。
dispatch_source_merge_data

//设置响应分派源事件的block,在分派源指定的队列上运行 dispatch_source_set_event_handler

//得到分派源的数据 dispatch_source_get_data

//得到dispatch源创建,即调用dispatch_source_create的第二个参数 uintptr_t
dispatch_source_get_handle(dispatch_source_t source);

//得到dispatch源创建,即调用dispatch_source_create的第三个参数 unsigned long
dispatch_source_get_mask(dispatch_source_t source)

取消dispatch源的事件处理–即不再调用block。如果调用dispatch_suspend只是暂停dispatch源。
void dispatch_source_cancel(dispatch_source_t source);

//检测是否dispatch源被取消,如果返回非0值则表明dispatch源已经被取消 long
dispatch_source_testcancel(dispatch_source_t source);

//dispatch源取消时调用的block,一般用于关闭文件或socket等,释放相关资源 void
dispatch_source_set_cancel_handler(dispatch_source_t source,
dispatch_block_t cancel_handler);

//可用于设置dispatch源启动时调用block,调用完成后即释放这个block。也可在dispatch源运行当中随时调用这个函数。
void dispatch_source_set_registration_handler(dispatch_source_t
source, dispatch_block_t registration_handler);

经常用于验证码倒计时,因为dispatch_source不依赖于Runloop,而是直接和底层内核交互,准确性更高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值