iOS面试精进iOS

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

2021-08-19  本文已影响0人  十年开发初学者

栅栏函数

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

栅栏函数使用

同步栅栏函数
    
    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_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,接下来分析下
栅栏函数底层分析

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

image.png

先来查看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_semaphore_t)

案例
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进行判断
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为什么要配合起来使用

上一篇 下一篇

猜你喜欢

热点阅读