美文网首页
iOS - GCD单例、栅栏、信号量

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

作者: Gumball_a45f | 来源:发表于2021-04-15 11:01 被阅读0次

    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单例、栅栏、信号量

          本文链接:https://www.haomeiwen.com/subject/socvkltx.html