GCD底层

2020-11-06  本文已影响0人  深圳_你要的昵称

前言

书接上回函数与队列,我们根据GCD底层源码分析,知道了队列分为串行并发两种类型,有两种常用的队列:主队列全局队列,其中主队列类型是串行,而全局队列类型是并发
队列的底层创建流程大致分析完了,今天我们再重点看看函数的底层执行流程,就是GCD的Block的初始化,以及被调用的过程。我们以异步并发为例看看👇

    dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(conque, ^{
        NSLog(@"12334");
    });

dispatch_async底层

#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
  1. 入参work就是block,需要执行的任务;
  2. 局部变量dc dc_flag都是为了初始化qos -->_dispatch_continuation_init(dc, dq, work, 0, dc_flags);
  3. 初始化完成后,接着_dispatch_continuation_async

dispatch_async的源码不多,就这么几行,但是我们知道,异步并发任务是会开启子线程的,在子线程去执行block任务,所以,我们需要关注2个点:

1 子线程的创建流程

我们先进入_dispatch_continuation_init的流程,看看是否有子线程创建?

1.1 _dispatch_continuation_init 底层流程

接着来到_dispatch_continuation_init_f,其中release的func是第4个入参,而work就是需要执行的任务是ctxt第3个入参,继续看看_dispatch_continuation_init_f

以上并没有发现关于子线程创建的代码,接下来我们去_dispatch_continuation_async找找。

1.2 _dispatch_continuation_async底层流程

根据返回值,锁定到了dx_push。全局搜索dx_push👇

继续看dq_push👇


如果是并发队列,那么.dq_push = _dispatch_lane_concurrent_push,接着来到
_dispatch_lane_concurrent_push👇

显然是非栅栏函数,那么进入_dispatch_continuation_redirect_push👇

do_targetq是什么呢?得回到队列的创建dispatch_queue_create去查看👇

那么,_dispatch_continuation_redirect_push里的dx_push时的队列是_dispatch_get_root_queue()👇

同理,找dispatch_queue_global_t 对应的 dq_push 的方法

至此,我们知道了dispatch_async子线程创建的调用链👇(层级比较深)

  1. dispatch_async --> _dispatch_continuation_async --> dx_push --> dq_push --> 并发队列:_dispatch_lane_concurrent_push --> _dispatch_continuation_redirect_push
  2. _dispatch_continuation_redirect_push --> dx_push(此时是global_queue) -->_dispatch_root_queue_push --> _dispatch_root_queue_push_inline-->_dispatch_root_queue_poke-->_dispatch_root_queue_poke_slow -->线程池调度,创建线程pthread_create

2 任务Block的调用流程

那么接下来的问题就是 block何时调用?

再看哪里调用的block --> 类似这样的代码block(xxx)? 我们可以在dispatch_async的任务block中打断点,然后bt查看调用栈👇

注意到在调用栈中有一个_dispatch_worker_thread2,现在重点就来到 --> 什么时候调起的_dispatch_worker_thread2

我们先全局搜索一下_dispatch_worker_thread2👇

发现全在一个方法里面 --> _dispatch_root_queues_init_once中,同理,全局搜索👇

再全局搜索_dispatch_root_queues_init 👇

_dispatch_root_queue_poke_slow是否很熟悉? 就是我们上面在查找创建子线程时调用栈走过的方法,那么此时任务block的调用和子线程的创建产生了联系,这个联系就是_dispatch_root_queue_poke_slow

2.1 _dispatch_root_queues_init

现在我们重点来看看_dispatch_root_queues_init,是否真的调用任务block?

DISPATCH_STATIC_GLOBAL(dispatch_once_t _dispatch_root_queues_pred);
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
    dispatch_once_f(&_dispatch_root_queues_pred, NULL,
            _dispatch_root_queues_init_once);
}

dispatch_once_f是否有些熟悉?莫非是单例?我们平时写的单例是这样的👇

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // input your code
    });

搜索一下dispatch_once源码👇

果然, dispatch_once_f是个单例方法。

2.2 单例的底层

dispatch_once_f源码👇

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);
    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_gate_tryenter判断条件,就能保证当前只有一个线程进去执行代码,那为什么只能执行一次呢?还是看_dispatch_once_gate_broadcast 里的 _dispatch_once_mark_done

2.3 回到Block的调用时机

即什么时候调起的_dispatch_worker_thread2
_dispatch_root_queues_init时,单例执行的任务block是_dispatch_root_queues_init_once👇


再来看看_dispatch_root_queues_init_once👇

上图可知,在_dispatch_root_queues_init_once中完成了线程与任务_dispatch_worker_thread2的绑定过程。接下来就看看_dispatch_worker_thread2的大致流程。

2.4 _dispatch_worker_thread2底层

最终,我们来到了dx_invoke_dispatch_continuation_invoke_inline

#define dx_invoke(x, y, z) dx_vtable(x)->do_invoke(x, y, z)

都是从dx_vtable中的属性而来。再搜索do_invoke👇

因为是并发队列queue_concurrent,对应的invoke方法名是_dispatch_lane_invoke👇

invoke是入参_dispatch_queue_class_invoke_handler_t类型,也就是方法_dispatch_lane_invoke2👇

又回到了_dispatch_continuation_pop_inline👇

此时肯定不会再次触发dx_invoke,不然就递归了,所以会走到_dispatch_continuation_invoke_inline,剩下的流程就是调用block()了。

至此,我们跟着底层源码弄清楚了block()的调用流程👇

  1. 通过在block任务中打断点,LLDB bt指令查看调用栈信息,找到_dispatch_worker_thread2
  2. 搜索调用_dispatch_worker_thread2的地方,找到_dispatch_root_queues_init --> _dispatch_root_queues_init_once
  3. 接着我们在_dispatch_root_queues_init_once中发现了子线程的创建,并绑定了block任务_dispatch_worker_thread2
  4. 接着我们继续查看_dispatch_worker_thread2的底层源码,发现了调用block任务的时机点。

总结

我们通过异步并发的案例,围绕两个点:子线程的创建时机任务Block的调用时机,分析了dispatch_async的底层大致流程,同时也关联到单例的底层实现原理。

补充1:栅栏函数

dispatch_barrier_(a)sync被称作栅栏函数,不论是同步栅栏还是异步栅栏,都必须等上面的任务执行完,栅栏函数本身的block任务才会执行,而同步与异步的差别在于👇

  1. 同步栅栏必须自己的block任务执行完成,下面的任务block才会执行,这就表示同步阻塞的是当前的线程
  2. 异步却不需要等自己的任务block执行,下面的代码会接着执行,这就表示异步阻塞的是队列(queue)

栅栏函数一个重要的点:必须是并发队列,并且是自定义的并发队列

不可用global_queue全局并发队列

示例👇

- (void)demo2{
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    // 这里是可以的额!
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"123");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"加载那么多,喘口气!!!");
    });
    NSLog(@"**********起来干!!");
 
}

运行👇

栅栏根本没有作用,按道理123应该优先currentThead的。

作用可等同于锁

示例👇

- (void)demo3{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            @synchronized (self) {
                [self.mArray addObject:image];
                NSLog(@"self.mArray添加image:%@", imageName);
            }
        });
    }
}

运行👇


因为是异步并发队列,所以会有很多子线程处理图片的添加过程,但是日志表明,当前的图片是一张张的添加进数组的,这就是锁的作用。
我们再换成栅栏函数👇

dispatch_barrier_async(concurrentQueue , ^{
    [self.mArray addObject:image];
    NSLog(@"self.mArray添加image:%@", imageName);
});

运行👇

一样的效果。

补充2:同步函数

dispatch_sync 沿着调用链,发现,如果是串行队列,那么就会走_dispatch_barrier_sync_f。👇

接着看_dispatch_barrier_sync_f的源码👇

再回头看看_dispatch_queue_try_acquire_barrier_sync里的流程👇

补充3:死锁问题

在主线程中运行下面代码:

dispatch_sync(mainQueue, ^{
    NSLog(@"123");
});

打断点,查看调用栈信息


会走到_dispatch_sync_f_slow,再看源码👇

接着看_dispatch_lock_is_locked_by

至此,最终会执行DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread");,直接crash报错。

这个错就是,主队列中添加同步执行的block任务,这个任务会交给当前的主线程去处理执行,但是因为是同步函数主线程又要等待block任务的执行完成,才能接着往下走,说白了,就是block的执行等待自己block的完成,矛盾了!这个矛盾的现象就被称作死锁

所以,这个死锁具体跟哪个队列是没有关系的,不管是主队列,还是其它自定义的串行队列,只要block任务自己等待自己完成,就会crash死锁!

补充4:信号量

信号量dispatch_semaphore_t实际运用的场景比较少,涉及的核心的函数有:

  1. dispatch_semaphore_create 创建信号量,指定最大并发数
  2. dispatch_semaphore_signal 发送信号
  3. dispatch_semaphore_wait 等待信号

示例👇

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });

运行👇


信号量的发送与等待,配合使用,可以保证当前任务的按一定顺序去执行。

我们现在来看看,dispatch_semaphore_signaldispatch_semaphore_wait底层源码是如何处理的?以dispatch_semaphore_signal为例👇

dispatch_semaphore_signal

接着我们看看os_atomic_inc2o👇

#define os_atomic_inc2o(p, f, m) \
        os_atomic_add2o(p, f, 1, m)

os_atomic_add2o👇

#define os_atomic_add2o(p, f, v, m) \
        os_atomic_add(&(p)->f, (v), m)

带入值,可知👇
os_atomic_inc2o(dsema, dsema_value, release) --> os_atomic_add2o(dsema, dsema_value, 1, release) --> os_atomic_add(dsema_value, 1, release)
接着看os_atomic_add

#define os_atomic_add(p, v, m) \
        _os_atomic_c11_op((p), (v), m, add, +)

#define _os_atomic_c11_op(p, v, m, o, op) \
        ({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })

接着:os_atomic_add(dsema_value, 1, release) --> _os_atomic_c11_op(dsema_value, 1, release, add, +) ,然后_os_atomic_c11_op是一个函数,重点看atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \ memory_order_##m)这句,再带入入参值,就是:atomic_fetch_add_explicit(3个入参)atomic_fetch_add_explicit是什么意思呢?👇详细请参考

综上,我们知道了dispatch_semaphore_signal就是++自加操作

dispatch_semaphore_wait

所以,dispatch_semaphore_signal表示信号量+1,dispatch_semaphore_wait表示信号量-1。如果在+1之前,信号量是-1的话,结果是0,那么就会进入signal_slow信号量永久等待的过程,同理,在wait之前信号量是0的话,减1结果是负1,也会进入signal_slow信号量永久等待的过程。

补充5:调度组

现在有一个场景:我们先要做很多的异步操作,例如网络请求,需要等待所有的异步网络请求执行完成后,才能执行下一步的操作。我们都知道,网络请求的执行完成,依赖当前网络的状态服务器响应的速度以及网络带宽的大小等其它很多因素,每个网络请求完成的时间是无法把控的,更何况要去找所有网络请求执行完成的这个时机点呢?很难吧!如果此时采用栅栏函数,你准备栏在哪里?此时,需要用到GDC另一个常用的API:dispatch_group_t调度组

我们先看看一个使用的示例:下载两张图片,其中一张是水印,两张图片下载完成后,再将两张图片整合生成一张带水印的图片展示👇

- (void)groupDemo {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        //创建调度组
        NSString *logoStr1 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        [self.mArray addObject:image1];
        dispatch_group_leave(group);
    });


    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        //创建调度组
       NSString *logoStr2 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        [self.mArray addObject:image2];
        dispatch_group_leave(group);
    });
   
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        UIImage *newImage = nil;
       NSLog(@"数组个数:%ld",self.mArray.count);
       for (int i = 0; i<self.mArray.count; i++) {
           UIImage *waterImage = self.mArray[i];
           newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
       }
        self.imageView.image = newImage;
    });

}

添加水印的代码👇

/**
 给指定图片加图片水印

 @param waterImage 水印图片
 @param rect 位置
 @return 返回图片水印照片
 */

+ (UIImage *)kc_WaterImageWithWaterImage:(UIImage *)waterImage backImage:(UIImage *)backImage waterImageRect:(CGRect)rect{
    
    UIImage *newBackImage = backImage;
    if (!newBackImage) {
        newBackImage = [UIImage imageNamed:@"backImage"];
    }
    return [self kc_WaterImageWithImage:newBackImage waterImage:waterImage waterImageRect:rect];
}

// 给图片添加图片水印
+ (UIImage *)kc_WaterImageWithImage:(UIImage *)image waterImage:(UIImage *)waterImage waterImageRect:(CGRect)rect{
    //1.获取图片
    //2.开启上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    //3.绘制背景图片
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    //绘制水印图片到当前上下文
    [waterImage drawInRect:rect];
    //4.从上下文中获取新图片
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    //5.关闭图形上下文
    UIGraphicsEndImageContext();
    //返回图片
    return newImage;
}
特殊1

再看一个相对简单的案例,但是我们不按常理出牌,将dispatch_group_notify写到最前面👇

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他们回来了,我准备在主线程更新UI");
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模拟耗时
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    NSLog(@"主线程任务正常运行");
}

run👇


事实证明-->把notify写在前面,发现会提前调用notify。why?

特殊2

接着我们把enter多写一个👇

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模拟耗时
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他们回来了,我准备在主线程更新UI");
    });
        
    NSLog(@"主线程任务正常运行");
}

run👇

可见,enter多了时,主线程被卡死了,但是没有crash。

特殊3

我们再多写一个leave,👇

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模拟耗时
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_leave(group);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他们回来了,我准备在主线程更新UI");
    });
        
    NSLog(@"主线程任务正常运行");
}

run👇

leave多了时,直接crash!

特殊4

我们使用dispatch_group_async,看看是打印什么?

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模拟耗时
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"7890");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他们回来了,我准备在主线程更新UI");
    });
        
    NSLog(@"主线程任务正常运行");
}

run👇

根据打印的日志,因为第一个group_async里有做一个sleep(1),所以456先打印,紧接着主线程的日志打印,再是dispatch_group_async的7890打印,然后123打印,最后notify的日志打印,那么就说明,在有dispatch_group_async的情况下,notify依旧最后执行,与有enterleave的情况是一样的。

带着上面几种现象,我们总结了以下3个问题👇

  1. dispatch_group_enter dispatch_group_leave 还有group_notify底层源码处理了哪些流程?
  2. dispatch_group_notify是如何接收dispatch_group_enterdispatch_group_leave传递的信息,然后执行block任务?
  3. 还有dispatch_group_async的底层源码做了什么?
dispatch_group_create底层

我们先看看dispatch_group_create

dispatch_group_t
dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}
dispatch_group_enter底层

看看os_atomic_sub_orig2o👇
#define os_atomic_sub_orig2o(p, f, v, m) \
        os_atomic_sub_orig(&(p)->f, (v), m)

#define os_atomic_sub_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, sub, -)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

注意第4个入参,和之前发送信号量dispatch_semaphore_signal的宏定义处理基本一模一样,那么得到的拼接函数名是atomic_fetch_sub_explicit👇

如果得到的old_value是0,那么接着进入_dispatch_retain👇

DISPATCH_ALWAYS_INLINE_NDEBUG
static inline void
_dispatch_retain(dispatch_object_t dou)
{
    (void)_os_object_retain_internal_n_inline(dou._os_obj, 1);
}
#define _os_object_refcnt_add_orig(o, n) \
        _os_atomic_refcnt_add_orig2o(o, os_obj_ref_cnt, n)

#define _os_atomic_refcnt_add_orig2o(o, m, n) \
        _os_atomic_refcnt_perform2o(o, m, add_orig, n, relaxed)

#define _os_atomic_refcnt_perform2o(o, f, op, n, m)   ({ \
        __typeof__(o) _o = (o); \
        int _ref_cnt = _o->f; \
        if (likely(_ref_cnt != _OS_OBJECT_GLOBAL_REFCNT)) { \
            _ref_cnt = os_atomic_##op##2o(_o, f, n, m); \
        } \
        _ref_cnt; \
    })

同样的替换,得到os_atomic_add_orig2o

#define os_atomic_add_orig2o(p, f, v, m) \
        os_atomic_add_orig(&(p)->f, (v), m)

#define os_atomic_add_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, add, +)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

再次替换,得到atomic_fetch_add_explicit,又是这个加1操作。
至此验证了,当从os底层哈希表中获取的当前group的INTERVAL值为0时,走_dispatch_retain,会+1,类似于一个加锁的操作,一直在此wait。

dispatch_group_leave底层

接着看看 os_atomic_add_orig2o
#define os_atomic_add_orig2o(p, f, v, m) \
        os_atomic_add_orig(&(p)->f, (v), m)

#define os_atomic_add_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, add, +)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

拼接函数名,得到atomic_fetch_add_explicit --> +1操作,和注释描述的一样incremented -1 -> 0 transition

下图是一个特殊情况,当enter多次时的处理👇

再接着看看_dispatch_group_wake

至此,我们知道了dispatch_group_enter 减一 dispatch_group_leave 加一,同时,当从os底层获取group的INTERVAL的值为DISPATCH_GROUP_VALUE_1时,会执行一个do-while循环处理新旧状态值,最终dispatch_group_leave会调用_dispatch_group_wake通知group任务block继续往下执行。

dispatch_group_notify底层

通过对dispatch_group_enterdispatch_group_leave的底层源码分析得知,只有leave时,old_value才会从-1 --> 0,所以dispatch_group_leave后,dispatch_group_notify才会调用_dispatch_group_wake通知group任务block继续往下执行。这里也就回答了问题1问题2的原因所在。

dispatch_group_async的底层

最后看看dispatch_group_async的源码👇

_dispatch_continuation_async我们再熟悉不过,之前在分析dispatch_async时,调用栈里也有它👇

接着dx_push,它会创建子线程,绑定任务_dispatch_worker_thread2,而_dispatch_worker_thread2就是负责调用group任务block的所在。

那么问题来了,_dispatch_continuation_async里有了dispatch_group_enter,却没有dispatch_group_leave,leave在哪里被调用呢?猜想,是否在任务block执行完成后?我们不妨验证一下,在block里打断点,bt查看其调用栈信息👇

_dispatch_call_block_and_release这个明显是block执行完成,release释放了,所以往下看_dispatch_client_callout,这个我们也知道,是触发任务block,再往下,看看_dispatch_queue_override_invoke👇

通过bt查看调用栈信息,查找到_dispatch_queue_override_invoke里,然后继续往下查找,最终找到了dispatch_group_leave

综上我们可以得出结论👇
dispatch_group_async = dispatch_group_enter + 子线程绑定,block任务调用 + dispatch_group_leave

以上就是对调度组dispatch_group_t几个常用api的底层源码的分析,我们现在已经清楚的知道了dispatch_group_enterdispatch_group_leave 必须配对使用,而dispatch_group_notify的执行,必须依赖dispatch_group_leave的唤醒。同时,dispatch_group_async就是简化版的dispatch_group_enterdispatch_group_leave

上一篇下一篇

猜你喜欢

热点阅读