美文网首页iOS面试精进iOS
OC底层原理探索—GCD(下)—— 栅栏函数、调度组、信号量

OC底层原理探索—GCD(下)—— 栅栏函数、调度组、信号量

作者: 十年开发初学者 | 来源:发表于2021-08-19 17:34 被阅读0次

    栅栏函数

    关于栅栏函数,系统提供了两个方法

    • dispatch_barrier_async
    • dispatch_barrier_sync
      dispatch_barrier_syncdispatch_barrier_async区别会不会阻塞当前的线程,要注意,栅栏函数只能控制同一队列。
      全局并发队列 :dispatch_get_global_queue会使栅栏函数失效

    栅栏函数使用

    同步栅栏函数
        
        dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
        /* 1.异步函数 */
        dispatch_async(concurrentQueue, ^{
            NSLog(@"1");
        });
        
        dispatch_async(concurrentQueue, ^{
            
            NSLog(@"2");
        });
        /* 2. 栅栏函数 */ 
        dispatch_barrier_sync(concurrentQueue, ^{
            NSLog(@"3");
        });
        /* 3. 异步函数 */
        dispatch_async(concurrentQueue, ^{
            NSLog(@"4");
        });
        // 4
        NSLog(@"5");
    
    image.png
    这里打印的结果为12354,接下来分析下
    • 因为这里是dispatch_barrier_sync同步栅栏函数,阻塞当前的线程,所以5一定是在3后面打印
    • 栅栏函数是在同一队列的任务,栅栏上方的任务先执行,当上方任务执行完毕再执行栅栏内部任务,最后执行栅栏下方任务
    • 所以1,2先打印,1,2的顺序不固定。接下来一定是打印3
    • 后面打印5,是因为dispatch_async本身存在耗时操作,4一定在5后面
    异步栅栏函数
        
        dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
        /* 1.异步函数 */
        dispatch_async(concurrentQueue, ^{
            NSLog(@"1");
        });
        
        dispatch_async(concurrentQueue, ^{
            
            NSLog(@"2");
        });
        /* 2. 栅栏函数 */ // 
        dispatch_barrier_async(concurrentQueue, ^{
            NSLog(@"3");
        });
        /* 3. 异步函数 */
        dispatch_async(concurrentQueue, ^{
            NSLog(@"4");
        });
        // 4
        NSLog(@"5");
    
    image.png
    这里的打印顺序为51234,接下来分析下
    • 观察打印顺序和同步栅栏函数唯一的区别是5的打印
    • 是因为异步栅栏函数不会阻塞当前线程,而dispatch_async存在耗时,所以5先打印,剩下的顺序与同步栅栏函数一致
    栅栏函数底层分析

    通过libdispatch源码进入函数dispatch_barrier_sync->_dispatch_barrier_sync_f->_dispatch_barrier_sync_f_inline
    进入_dispatch_barrier_sync_f_inline

    image.png
    • _dispatch_sync_f_slow
    • _dispatch_sync_recurse
    • _dispatch_lane_barrier_sync_invoke_and_complete
      这里有这3个方法我们不知道最终进入哪个,我们通过符号断点来确认
      image.png
      进入_dispatch_sync_f_slow
      image.png
      继续跟踪流程,并添加_dispatch_sync_invoke_and_complete_recurse的符号断点,并进入
      image.png
      进入_dispatch_sync_complete_recurse
      image.png
      这里我们发现是个do while循环,这里思考下为什么这么写?
    • 栅栏函数起到的的是同步作用,同一队列中,栅栏前的任务没有执行栅栏函数是不会走的
    • 所以这里需要递归处理栅栏函数前面的任务
    • _dispatch_sync_complete_recurse中的递归中,先判断barrier是否存在,如果存在则需要先把栅栏前的任务dx_wakeup 全部唤醒。唤醒成功后才会执行_dispatch_lane_non_barrier_complete

    先来查看dx_wakeup,来查看barrier什么时候被移出,dx_wakeup是通过宏定义的函数

    #define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
    

    搜索dq_wakeup

    image.png
    由上图可知串行队列和并行队列都走了_dispatch_lane_wakeup,而全局并发队列走了_dispatch_root_queue_wakeup
    • 串行、并行队列,进入_dispatch_lane_wakeup
      image.png
      进入_dispatch_lane_barrier_complete
      image.png
    • 如果是串行队列则会进行等待,知道其他任务执行完成,按顺序执行
    • 如果是并行队列,调用_dispatch_lane_drain_non_barriers将栅栏前的任务按照异步的放心执行
    • 全局并发队列
      当是全局并发队列的时候,进入_dispatch_root_queue_wakeup
      image.png
      由上图可知,全局并发队列并没有栅栏函数的相关处理流程,这也是栅栏函数在全局并发队列失效的原因

    【问题】为什么全局并发队列中不对栅栏函数进行处理
    【答】因为全局并发队列除了被我们使用,系统也在使用,如果添加了栅栏函数,会导致队列运行的阻塞,从而影响系统级的运行,所以栅栏函数也就不适用于全局并发队列。

    信号量(dispatch_semaphore_t)

    • dispatch_semaphore_create创建一个Semaphore,并初始信号总量
    • dispatch_semaphore_wait信号量减1,当信号量小于0时,就会所在线程发生阻塞,大于等于0时,正常执行
    • dispatch_semaphore_signal信号量加1
    案例
    image.png
    这里我i们的正常理解应该是先执行任务1,但是这里初始化的信号总量为0,且在任务1中dispatch_semaphore_wait起到了加锁作用,所以先去执行任务2,且发出了dispatch_semaphore_signal解锁信号,再去执行任务`
    信号量底层分析

    dispatch_semaphore_wait

    image.png
    首先对信号量做减1操作,当信号量大于等于0时直接返回,否则进入_dispatch_semaphore_wait_slow方法
    image.png
    timeout进行判断
    • 如果是默认的,会直接跳出
    • DISPATCH_TIME_NOW,会进行一个超时处理
    • DISPATCH_TIME_FOREVER会进入_dispatch_sema4_wait方法
      进入_dispatch_sema4_wait方法
    void
    _dispatch_sema4_wait(_dispatch_sema4_t *sema)
    {
        int ret = 0;
        do {
            ret = sem_wait(sema);
        } while (ret == -1 && errno == EINTR);
        DISPATCH_SEMAPHORE_VERIFY_RET(ret);
    }
    

    我们发现有个do while方法,并调用sem_wait,全局搜索sem_wait

    image.png
    并没有搜索出sem_wait方法的实现
    所以_dispatch_sema4_waitdo while就是个死循环,原因就是要让该任务一致处于等待状态

    dispatch_semaphore_signal

    image.png
    通过os_atomic_inc2o对信号量做+1操作,如果大于0直接返回。
    如果加过一次后仍小于0,则会抛出异常Unbalanced call to dispatch_semaphore_signal()并调用_dispatch_semaphore_signal_slow方法,见下图:
    image.png
    这里会开启一个循环,对信号量加一操作,知道满足条件位置

    【总结】信号来那个在实际开发中的作用

    • 保持线程同步,将异步执行的任务,转换成同步操作
    • 保证线程安全,为线程加锁(自旋锁)

    调度组

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

    dispatch_group_enter 进组
    dispatch_group_leave 出组
    两者搭配使用

    案例实现1
    image.png
    我们在异步线程中加了个sleep(2),这个时候在主线程打印为空数组,但是在我们的dispatch_group_notify中是能够打印出数组的内容。是因为在相同组中的任务,都执行完毕后会走dispatch_group_notify该方法。
    【注意】dispatch_group_enterdispatch_group_leave要成对出现,并且dispatch_group_enter在执行任务前,dispatch_group_leave任务执行完成后调用,否则顺序错误会报错
    案例实现2
    image.png
    这里直接将任务放在dispatch_group_async,最终结果和上述案例相同,其实dispatch_group_async就是底层封装了dispatch_group_enter 和dispatch_group_leave
    调度组底层分析

    dispatch_group_create

    image.png
    调用_dispatch_group_create_with_count并将信号量默认传0
    image.png
    通过os_atomic_store2o进行保存

    dispatch_group_enter

    image.png
    默认信号量为0 ,所以信号量减1,由0-1,old_bits等于-1

    dispatch_group_leave

    image.png
    信号量加1,此时的newState等于0oldState等于-1
    #define DISPATCH_GROUP_VALUE_MASK       0x00000000fffffffcULL
    #define DISPATCH_GROUP_VALUE_1          DISPATCH_GROUP_VALUE_MASK
    

    old_state & DISPATCH_GROUP_VALUE_MASK等于0,即old_value等于0
    也就是 old_value 与DISPATCH_GROUP_VALUE_1不会相等,最终调用if中的 return _dispatch_group_wake_dispatch_group_wake也就是去唤醒dispatch_group_notify

    dispatch_group_notify

    image.png
    这里判断old_state == 0就去唤醒函数的执行流程,在上一步已经分析出old_state = 0

    所以这里也就解释了dispatch_group_enter和dispatch_group_leave为什么要配合起来使用

    相关文章

      网友评论

        本文标题:OC底层原理探索—GCD(下)—— 栅栏函数、调度组、信号量

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