前言
上片文章分析了GCD队列和函数的使用方式、串行队列和并发队列的创建、同步函数和异步函数底层执行流程、串行队列的死锁、GCD单例的实现流程等。这篇文章我们继续探究dispatch_barrier栅栏函数、dispatch_semaphore信号量、dispatch_group调度组、dispatch_source事件源等,将从使用和底层原理两个角度去分析这些内容。
准备工作
1. 栅栏函数
1.1 常用的栅栏函数
- 
dispatch_barrier_async
前面的任务执行完毕才会执行barrier中的逻辑,以及barrier后加入队列的任务。 - 
dispatch_barrier_sync
作用相同,但是会堵塞线程,影响后面的任务执行。 
区别:dispatch_barrier_sync和dispatch_barrier_async的区别也就在于会不会阻塞当前线程,同时需要注意的是,栅栏函数只能控制同一并发队列。
1.2 栅栏函数的使用
自定义了一个并发队列,并且添加3个异步函数,加下面代码:
- (void)demo{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    /* 2. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        sleep(0.5);
        NSLog(@"2");
    });
//    // 栅栏函数
//    dispatch_barrier_async(concurrentQueue, ^{
//        NSLog(@"----%@-----", [NSThread currentThread]);
//    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
}
运行结果还是很明确,因为该队列是一个并发队列,并且是异步函数,所以任务1、任务2、任务3、任务4的执行顺序是混乱的。见下面运行结果:
执行结果
- 
添加栅栏函数
dispatch_barrier_sync
如果现在有一个需求,确保任务1和任务2先执行,才能执行任务3,可以添加一个栅栏函数,见下面代码:
添加dispatch_barrier_sync
分析:
任务1和任务2一定会先于栅栏函数运行,在栅栏函数运行之后,才会运行任务3。同时dispatch_barrier_sync还有另外一个特点,会堵塞当前的线程,所以任务4会在栅栏函数执行后才会被执行。 - 
添加栅栏函数
dispatch_barrier_async
添加dispatch_barrier_async
分析:
添加一个栅栏函数dispatch_barrier_async,运行发现,该并发队列中的任务1和任务2一定会先于栅栏函数运行,在栅栏函数运行之后,才会运行任务3。因为任务4是在主队列,所以并不影响任务4的正常执行。 - 
注意:
- 
dispatch_barrier_sync会阻塞当前线程 - 
栅栏函数和其他的任务必须在同一个队列中 - 
不能使用全局并发队列(后面会分析) 
 - 
 
1.3 栅栏函数的底层原理
我们对栅栏函数的任务无非就是栅栏函数起到同步的作用,全局并发队列不能够执行栅栏函数。那我们分析一下源码,看看源码是怎么样的逻辑,请往下走。
在libdispatch.dylib源码中全局搜索dispatch_barrier_sync,一路往下跟踪最终找到了_dispatch_barrier_sync_f_inline方法中,如下图:
_dispatch_barrier_sync_f_inline
通过下符号断点
_dispatch_sync_f_slow,成功进入了该方法,说明栅栏函数是进入以上判断的,如下图:
下_dispatch_sync_f_slow符号断点
_dispatch_sync_f_slow方法在之前同步函数执行和死锁时候已经分析过,同时在调用这个方法时设置了DC_FLAG_BARRIER的标签。_dispatch_sync_f_slow方法见下图:
_dispatch_sync_f_slow
因为
func基本不会为NULL,那我们添加_dispatch_sync_invoke_and_complete_recurse符号断点,发现的确进入了这个方法,如下:
下_dispatch_sync_invoke_and_complete_recurse符号断点
通过上面的运行堆栈,发现其流程为:
_dispatch_sync_f_slow -> _dispatch_sync_invoke_and_complete_recurse -> _dispatch_sync_complete_recurse,最终定位到_dispatch_sync_complete_recurse方法,见下图:
_dispatch_sync_complete_recurse
分析:
栅栏函数的作用是起到同步,也就是说队列中之前的任务没有执行完,栅栏函数肯定是不会走的。所以在进行栅栏函数调用之前,肯定是要进行递归处理,完成队列中的任务在
_dispatch_sync_complete_recurse方法中,进行了递归处理,如果当前存在barrier,则会将当前队列中的任务全部唤醒执行,调用dx_wakeup。唤醒执行完毕后,才会执行_dispatch_lane_non_barrier_complete,即当前队列任务已经执行完成了,并且没有栅栏函数,执行下面的流程。
想要执行栅栏函数之后的任务栅栏函数要先移除,那么栅栏函数在哪里被执行或者被移除的呢?跟踪dx_wakeup执行流程。dx_wakeup是通过宏定义的函数,全局搜索并找到了定义的位置,见下图:
dx_wakeup
之前我们已经说过,
底层为不同类型的队列提供不同的调用入口,那为什么全局并发队列不能够用栅栏函数呢?继续往下看!
自定义并发队列
自定义并发队列会调用_dispatch_lane_wakeup方法,定位源码,见下图:
_dispatch_lane_wakeup
首先会判断是否为
barrier形式,如果是,则会调用_dispatch_lane_barrier_complete方法,处理有栅栏函数的流程;如果没有,则走正常的并发队列流程,调用_dispatch_queue_wakeup方法。
进入_dispatch_lane_barrier_complete方法,查看流程,如下:
_dispatch_lane_barrier_complete
分析:
如果是
串行队列,则会进行等待,直到其他的任务执行完成,按顺序执行;如果是并发队列,则会调用_dispatch_lane_drain_non_barriers将栅栏之前的任务执行完成。最终调用_dispatch_lane_class_barrier_complete方法,完成栅栏的清除,从而执行栅栏之后的任务。
全局并发队列
如果是全局并发队列,dx_wakeup方法对应的是_dispatch_root_queue_wakeup方法,查看_dispatch_root_queue_wakeup源码实现,见下图:
_dispatch_root_queue_wakeup
在全局并发队列流程中,
并没有栅栏函数的相关处理流程,也就是按照正常的并发队列来处理。总结:
全局并发队列为什么没有对栅栏函数进行处理呢?
因为全局并发队列除了被我们使用,系统也在使用,如果添加了栅栏函数,会导致队列运行的阻塞,从而影响系统级的运行,所以栅栏函数也就不适用于全局并发队列。
2. 信号量
在使用GCD过程中我们也会用到信号量(Dispatch Semaphore),持有计数的信号。Dispatch Semaphore提供了三个函数。
- 
dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量 - 
dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行 - 
dispatch_semaphore_signal:发送一个信号,让信号总量加1,解锁 
查看dispatch_semaphore_create的API相关说明如下:
dispatch_semaphore_create
我们可以得出结论,信号量如果
大于0,表示可以控制GCD的最大并发数。
2.1 信号量的使用
- 案例1
 
    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); // 等待
        sleep(2);
        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    // 任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待、
        sleep(2);
        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
在全局并发队列中,异步执行相关的任务,当前Semaphore的初始值为1,也就是说当前队列最大并发数为1。dispatch_semaphore_wait表示阻塞,或者说占用一个信号,dispatch_semaphore_signal表示释放,也就是释放所占用的信号。
- 
案例2
对上面的案例进行一些调整,我们将信号量初始值变为0,也就是最大并发数设置为0。异步并发执行两个任务,并且任务延迟了2秒钟,见下面代码:
案例2
如果没有加入信号量的话,一般情况都会先执行任务1然后再执行任务2。但是实际的情况相反。这里dispatch_semaphore_wait有加锁的作用,而dispatch_semaphore_signal有解锁作用。当执行任务1时,dispatch_semaphore_wait加锁进行等待,当任务2执行完毕后,dispatch_semaphore_signal解锁发出信号,其他的任务可以执行,起到控制流程的作用。 - 
案例3
信号量初始值变为0,也就是最大并发数设置为0。dispatch_semaphore_wait在主线程中,异步流程中停顿2秒钟,正常情况下应该会先执行打印操作,number输出等于0才对,但是实际的情况是number等于1。见下图代码:
案例3
 
原因和案例2是一致的,dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行,number输出结果为1。
2.2 信号量原理分析
我们探究原理肯定是带着目的去的,那么我们以下就是主要探索dispatch_semaphore_wait和dispatch_semaphore_signal加锁和解锁功能是如何实现的,跟着走吧。
2.2.1 dispatch_semaphore_wait原理
在libdispatch.dyld中查找其实现源码如下:
dispatch_semaphore_wait
分析:
os_atomic_dec2o进行减操作,也就是对创建是传入的value值进行减操作。以此来控制可并发数。如果可并发数为
3,则调用该方法后,变为2,表示占用一个并发数,剩下还可同时执行2个任务。但是,如果初始值是0,减操作之后为负数,则会调动_dispatch_semaphore_wait_slow方法。
_dispatch_semaphore_wait_slow实现如下:
_dispatch_semaphore_wait_slow
上面的案例中我们调用
dispatch_semaphore_wait时,传入的flag为DISPATCH_TIME_FOREVER,表示一直等待。进入_dispatch_sema4_wait实现流程,如下图:
_dispatch_sema4_wait
分析:
由上图看出
_dispatch_sema4_wait的实现是在lock(锁)的相关文件,可以知道_dispatch_sema4_wait是对锁进行操作的。_dispatch_sema4_wait进行do-while循环,当不满足条件时,会一直循环下去,从而导致流程的阻塞。这也就解释了上面案例2和案例3的执行结果。
2.2.2 dispatch_semaphore_signal原理
其实现代码如下:
dispatch_semaphore_signal
os_atomic_inc2o是加操作,也就是对可用并发数据进行释放,将dispatch_semaphore_wait获取的一个执行权限释放掉。当信号量初始值是
0时,调用加操作后,value值大于0,这样就可以获得执行权限。但是如果加一次后依然小于0,则会报异常:Unbalanced call to dispatch_semaphore_signal()。并调用_dispatch_semaphore_signal_slow方法。_dispatch_semaphore_signal_slow实现如下:
_dispatch_semaphore_signal_slow
_dispatch_sema4_signal同样会开启一个do-while循环,直到满足条件可以运行为止。
_dispatch_sema4_signal
Dispatch Semaphore总结:
- 保持线程同步,将异步执行任务转换为
同步执行任务 - 保证
线程安全,为线程加锁 
3. 调度组
dispatch_group,主要作用是控制任务的执行顺序。提供了以下方法:
- 
dispatch_group_create创建组 - 
dispatch_group_async进组任务并执行 - 
dispatch_group_notify进组任务执行完毕通知 - 
dispatch_group_wait进组任务执行等待时间 - 
dispatch_group_enter进组 - 
dispatch_group_leave出组 
注意:dispatch_group_enter与dispatch_group_leave必须要成对使用
3.1 调度组的使用
- 
调度组案例
要求完成任务1、任务2、任务3之后才能执行任务4。使用调度组可以采用以下方式:
调度组案例
各个queue加到group里,然后当组中任务完成后再调用任务4,这里使用了dispatch_group_wait进行等待。dispatch_group_wait()函数会一直等到前面group中的内容执行完再执行下面内容,但会产生阻塞线程的问题。这也就导致了主线程中的任务5不能正常运行,直到任务组的任务完成才能被调用。 - 
dispatch_group_notify的使用
为解决上面的问题,可采用dispatch_group_notify进行任务执行完毕的通知,见下图:
dispatch_group_notify的使用
采用这种方式后,任务5不会被阻塞,当任务组中的任务执行完毕后,再通知任务4执行。 - 
进组出组的使用
dispatch_group_enter与dispatch_group_leave搭配使用也可以完成上面的效果,见下图:
进出组的使用
一个enter必须对应一个leave,成对出现!当所有任务都执行完成并出组后,才会执行任务4,并且不会阻塞任务5的执行。 
如果enter和leave没有成对出现,比如多了一个leave则会崩溃,见下图:
崩溃案例
如果多一个进组
enter,则后续的任务则不能正常运行。见下图:
不能运行案例
3.2 调度组底层原理分析
dispatch_group_enter进组和dispatch_group_leave出组为什么能够起到与调度组dispatch_group_async一样的效果呢?
- 
dispatch_group_create
dispatch_group_create方法实现见下图:
dispatch_group_create
会调用_dispatch_group_create_with_count方法,并默认传入0,_dispatch_group_create_with_count的实现见下图:
_dispatch_group_create_with_count
通过os_atomic_store2o进行保存。 - 
dispatch_group_enter
查看dispatch_group_enter实现源码,见下图:
dispatch_group_enter
os_atomic_sub_orig2o会进行--减减操作,此时的old_bits等于-1。 - 
**
dispatch_group_leave**
查看dispatch_group_leave实现源码,见下图:
dispatch_group_leave
这里通过os_atomic_add_orig2o,++加加操作获取了old_state,此时old_state就等于0。而0&DISPATCH_GROUP_VALUE_MASK依然等于0,也就是old_value等于0。与此同时,DISPATCH_GROUP_VALUE_1的定义见下面代码: 
#define DISPATCH_GROUP_VALUE_MASK       0x00000000fffffffcULL
#define DISPATCH_GROUP_VALUE_1          DISPATCH_GROUP_VALUE_MASK
#define DISPATCH_GROUP_VALUE_MASK       0x00000000fffffffcULL
很显然old_value是不等于DISPATCH_GROUP_VALUE_MASK的,所以流程会进入到外层的if中,并调用_dispatch_group_wake方法进行唤醒,唤醒的就是dispatch_group_notify方法,也就是说,如果不调用dispatch_group_leave方法,也就不会唤醒dispatch_group_notify,下面的流程也就不会执行。
- 
dispatch_group_notify
查看dispatch_group_notify源码发现,在old_state等于0的情况下,才会去唤醒相关的同步异步函数执行流程。见下图:
dispatch_group_notify
在dispatch_group_leave分析中,我们已经得到old_state结果等于0
所以这里也就解释了dispatch_group_enter和dispatch_group_leave为什么要配合起来使用的原因,通过信号量的控制,避免异步的影响,能够及时唤醒并调用dispatch_group_notify方法。 - 
dispatch_group_async的封装
为什么说dispatch_group_async就等于dispatch_group_enter和dispatch_group_leave呢?一起探究一下dispatch_group_async封装。
dispatch_group_async的定义,见下图:
dispatch_group_async
进入_dispatch_continuation_group_async方法如下:
_dispatch_continuation_group_async
在调用dispatch_group_async方法向组中添加任务时,就调用了dispatch_group_enter方法,将信号量0变成了-1。
那么如果需要将信号量重置,一定是在任务执行完毕后再调用dispatch_group_leave方法。继续跟踪代码,调用_dispatch_continuation_async方法,其源码实现见下图:
_dispatch_continuation_async
又回到了异步函数的流程了!具体异步函数分析过程见iOS GCD底层分析(1),这里不再跟踪分析。 
异步函数最终会调用_dispatch_worker_thread2方法,那么我们查看堆栈信息得到如下:
队列组堆栈信息
跟踪流程会调用
_dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline方法。
先进入_dispatch_root_queue_drain方法,如下:
_dispatch_root_queue_drain
跟踪进入到
_dispatch_continuation_pop_inline方法,如下:
_dispatch_continuation_pop_inline
跟踪进入到
_dispatch_continuation_invoke_inline方法,如下:
_dispatch_continuation_invoke_inline
跟踪进去
_dispatch_continuation_with_group_invoke方法,如下:
_dispatch_continuation_with_group_invoke
在这里完成
_dispatch_client_callout函数调用后,紧接着调用dispatch_group_leave方法,将信号量由-1变成了0。
注意:到此已经完整的分析了调度组、进组、出组、通知的底层原理和关系。
4. 事件源
在日常的开过程中,我们经常会用到NSTimer。NSTimer需要加入到NSRunloop中,还受到mode的影响。在mode设置不对的情况下,scrollView滑动的时候NSTimer也会收到影响。如果Runloop正在进行连续性的运行,timer就可能会被延迟。
GCD提供了一个解决方案dispatch_source源。dispatch_source有以下几种特性:
- 时间较准确,
CPU负荷小,占用资源少 - 可以使用
子线程,解决定时器跑在主线程上卡UI问题 - 可以暂停,继续,不用像
NSTimer一样需要重新创建 
dispatch_source源的关键方法:
- 
dispatch_source_create创建源 - 
dispatch_source_set_event_handler设置源事件回调 - 
dispatch_source_merge_data源事件设置数据 - 
dispatch_source_get_data获取源事件数据 - 
dispatch_resume继续 - 
dispatch_suspend挂起 
4.1 事件源的使用
- 创建事件源
 
// 方法声明
dispatch_source_t dispatch_source_create(
        dispatch_source_type_t type,
        uintptr_t handle,
        unsigned long mask,
        dispatch_queue_t _Nullable queue);
// 实现过程
dispatch_source_t source =  dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,  dispatch_get_main_queue());
创建过程需要传入两个重要的参数:
- 
dispatch_source_type_t要创建的源类型 - 
dispatch_queue_t事件处理程序块将提交到的调度队列 
事件源类型:
- 
DISPATCH_SOURCE_TYPE_DATA_ADD用于合并数据 - 
DISPATCH_SOURCE_TYPE_DATA_OR按位OR用于合并数据 - 
DISPATCH_SOURCE_TYPE_DATA_REPLACE新获得的数据值替换现有的 - 
DISPATCH_SOURCE_TYPE_MACH_SEND监视Mach端口的调度源,只有发送权,没有接收权
-DISPATCH_SOURCE_TYPE_MACH_RECV监视Mach端口的待处理消息 - 
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE监控系统的变化,内存压力状况 - 
DISPATCH_SOURCE_TYPE_PROC监视外部进程的事件的调度源 - 
DISPATCH_SOURCE_TYPE_READ监控文件描述符的调度源可供读取的字节 - 
DISPATCH_SOURCE_TYPE_SIGNAL用于监视当前进程的信号 - 
DISPATCH_SOURCE_TYPE_TIMER基于计时器的调度源 - 
DISPATCH_SOURCE_TYPE_VNODE监视事件文件描述符的调度源 - 
DISPATCH_SOURCE_TYPE_WRITE监视事件,写入字节的缓冲区空间 - 
事件源案例
使用dispatch_source设计一个计时器,1秒钟执行一次,能够暂停、开始,同时不受主线程影响。见下图实现代码: 
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger souceComplete;
@property (nonatomic) BOOL isRunning;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.souceComplete = 0;
    
    // 开始时间
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
    // 间隔时间
    uint64_t interval = 1.0 * NSEC_PER_SEC;
    
    // source
    self.queue = dispatch_queue_create("test", NULL);
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    // 设置计时器
    dispatch_source_set_timer(self.source, start, interval, 0);
    __weak __typeof(self) weakSelf = self;
    dispatch_source_set_event_handler(self.source, ^{
        NSLog(@"source --- %lu    ------  %@", (unsigned long)weakSelf.souceComplete++, [NSThread currentThread]);
    });
    // 默认启动
    self.isRunning = YES;
    dispatch_resume(self.source);
}
// 计时器控制
- (IBAction)didClickStartOrPauseAction:(id)sender {
    if (self.isRunning) {
        dispatch_suspend(self.source);
        dispatch_suspend(self.queue);
        self.isRunning = NO;
        [sender setTitle:@"暂停中.." forState:UIControlStateNormal];
    }else{
        dispatch_resume(self.source);
        dispatch_resume(self.queue);
        self.isRunning = YES;
        [sender setTitle:@"计时中.." forState:UIControlStateNormal];
    }
}
@end
- 
运行案例
事件源案例
 - 
注意事项:
 - 
Dispatch Source Timer是间隔定时器,也就是说每隔一段时间间隔定时器就会触发。在NSTimer中要做到同样的效果需要手动把repeats设置为YES。 - 
dispatch_source_set_timer中第二个参数,当我们使用dispatch_time或者DISPATCH_TIME_NOW时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用dispatch_walltime可以让计时器按照真实时间间隔进行计时。 - 
dispatch_source_set_timer的第四个参数leeway指的是一个期望的容忍时间,将它设置为1秒,意味着系统有可能在定时器时间到达的前1秒或者后1秒才真正触发定时器。在调用时推荐设置一个合理的leeway值。需要注意,就算指定leeway值为0,系统也无法保证完全精确的触发时间,只是会尽可能满足这个需求。 - 
event handler block中的代码会在指定的queue中执行。当queue是后台线程的时候,dispatch timer相比NSTimer就好操作一些了。因为NSTimer是需要Runloop支持的,如果要在后台dispatch queue中使用,则需要手动添加Runloop。使用dispatch timer就简单很多了。 - 
dispatch_source_set_event_handler这个函数在执行完之后,block会立马执行一遍,后面隔一定时间间隔再执行一次。而NSTimer第一次执行是到计时器触发之后。这也是和NSTimer之间的一个显著区别。 - 
停止
source
停止Dispatch Source有两种方法,但是这两种方式在使用时有很大的区别:- dispatch_suspend
 - dispatch_source_cancel
 
 
使用dispatch_suspend时,source本身的实例需要一直保持。dispatch_suspend之后的source,是不能被释放的,如果释放会崩溃,见下图:
释放source案例
使用
dispatch_source_cancel则没有这个限制,dispatch_source_cancel是真正意义上的取消source。被取消之后如果想再次执行source,只能重新创建新的source。这个过程类似于对NSTimer执行invalidate。见下图:
cance案例
- 
source挂起计数说明
dispatch_suspend严格上只是把source暂时挂起,它和dispatch_resume是一个平衡调用,两者分别会减少和增加dispatch对象的挂起计数。当这个计数大于0的时候,source就会执行。在挂起期间,产生的事件会积累起来,等到dispatch_resume的时候会融合为一个事件发送。 
- 
重复启动一个正在执行的源会崩溃
重复执行源
 - 连续挂起,同样需要连续对应次数的启动才能够正常运行
连续挂起source
注意:dispatch source并没有提供用于检测source本身的挂起计数的API,也就是说外部不能得知一个source当前是不是挂起状态,在设计代码逻辑时需要考虑到这两点。 
4.2 事件源底层原理分析
通常我们分析原理都是带着问题触发的,那么这次我们探索根据以上的问题:为什么source在运行时,重复调用dispatch_resume方法就会崩溃?在以下我们看看底层原理就一清二楚了。
查找dispatch_resume的底层实现原理,如下图:
dispatch_resume
接着进去
_dispatch_lane_resume方法查看源码,如下:
重复resume
重复
resume直接进入到了over_resume方法里面,查看其实现如下:
over_resume
通过解读源码发现,底层会对事件源的
相关状态进行判断,如果其进行过度恢复,则会走到over_resume流程,直接调起DISPATCH_CLIENT_CRASH崩溃。同时这里还维护了
挂起计数(old_state),挂起计数包含所有挂起和非活动位的挂起计数。下溢意味着需要过度恢复或暂停计数转移到边计数,也就是说如果当前计数器还没有到可运行的状态,需要连续恢复。
- 连续挂起
我们发现,连续挂起后需要对应次数的恢复过程才能执行,那么底层肯定是维护了一个信号量。首先搜索dispatch_suspend的实现,见下图:
dispatch_suspend
接着进去_dispatch_lane_suspend方法查看源码的实现,如下:
_dispatch_lane_suspend
通过下符号断点发现会进入_dispatch_lane_suspend_slow的流程,源码实现如下:
_dispatch_lane_suspend_slow
果不其然,同样这里维护一个暂停计数,如果连续调用挂起方法,则会进行减法的无符号下溢。 
总结
花了不少的时间,GCD的探索就到此结束了,过程好艰辛但是收获也是满满的。iOS的底层学习任重而道远,继续努力。











网友评论