iOS - GCD单例、栅栏、信号量

1.单例

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"单例应用");
});

平常我们会用到上面的代码来创建单例。

单例的特点是单例只执行一次

那他底层是如何处理的,让他只执行一次?且他的block哪时候调用的?

带着这个问题,进入单例的底层来看看。

  • dispatch_once底层是dispatch_once_f实现的

    • 参数1:onceToken是一个静态变量,具有唯一性
    • 参数2:block
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
  • 进入dispatch_once_f源码

内部处理

  • 将val(就是外部的onceToken) 转换为dispatch_once_gate_t类型l

  • os_atomic_load获取任务标识符v

    • if (likely(v == DLOCK_ONCE_DONE)),任务已执行,return

      • 如果加锁失败了,则走到_dispatch_once_mark_done_if_quiesced函数,再次进行存储,将标识符置为DLOCK_ONCE_DONE
    • else通过_dispatch_once_gate_tryenter解锁,然后_dispatch_once_callout执行block回调

  • 若此时有任务进入,则进入等待_dispatch_once_wait

DISPATCH_NOINLINE
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);//load
    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 的参数onceToken,onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return

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

2.栅栏函数

栅栏函数的主要作用:控制队列的执行顺序(但只能控制同一并发队列的顺序)

栅栏函数主要有:

  • 同步栅栏dispatch_barrier_sync(会阻塞线程)

  • 异步栅栏dispatch_barrier_async(不阻塞线程,只阻塞队列,必须是自定义的并发队列)

通俗的讲就是栅栏在两个执行语句中间,隔开

在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,没有任何意义

3.信号量

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

  • dispatch_semaphore_create创建信号量
  • dispatch_semaphore_wait上锁
  • dispatch_semaphore_signal解锁
//信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);

信号量:其实是可以在不同队列中使用同一个信号量的,
也可以让异步并行队列按顺序执行。

4.调度组

调度组的最直接作用是控制任务执行顺序

常规使用方法:

dispatch_group_create 创建组 
dispatch_group_async 进组任务 
dispatch_group_notify 进组任务执行完毕通知 dispatch_group_wait 进组任务执行等待时间

//进组和出组一般是成对使用的
dispatch_group_enter 进组 
dispatch_group_leave 出组

dispatch_group_enter 进组 ,dispatch_group_leave 出组是成对出现的,否则dispatch_group_notify 不调用,

dispatch_group_async 等同于enter - leave,其底层的实现就是enter-leave

dispatch_group_async :

但如果group的block内部有其他异步并行队列就不会等待了,会直接执行dispatch_group_notify

而dispatch_group_enter、和dispatch_group_leave则不会有这个问题

你可能感兴趣的:(iOS - GCD单例、栅栏、信号量)