GCD源码分析

977 阅读9分钟
  • 常见数据结构

    • dispatch_object_t
      GCD所有类的基类 image.png 从源码可以看出dispatch_object_t是一个联合体(而且是一个透明联合体),就是dispatch_object_t可以是结构体中的任意一种类型,大小为这些数据类型中最大的数据类型的大小
    • _os_object_s
      类似于类中的isa指针 image.png
    • dispatch_object_s
      GCD最基础的基类 image.png
    • dispatch_continuation_s
      dispatch_continuation_s结构体主要封装blockfunctiondispatch_async中的block最终都会封装成这个数据类型 image.png
    • dispatch_group_s
      image.png
    • dispatch_queue_s
      image.png
  • 创建队列源码分析

    • dispatch_queue_create源码
      第一步下一个符号断点 image.png 发现源码在libdispatch.dylib库中,然后下载libdispatch.dylib源码搜索dispatch_queue_create可以找到image.png其实冲堆栈信息中也可以知道接下来调用了_dispatch_lane_create_with_target函数来实现创建
    • _dispatch_lane_create_with_target源码
      image.png 大致步骤如下:
      1. 规范化参数
      2. 设置目标队列
        调用_dispatch_get_root_queue方法获取目标队列
        如果是串行队列,则使用_dispatch_get_root_queue(0, true)函数获取目标队列,获取到的目标队列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY]
        如果是并发队列,则使用_dispatch_get_root_queue(0, false)函数获取目标队列,获取到的目标队列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY]
      3. 开辟空间创建队列
        _dispatch_object_alloc开辟空间创建队列
      4. 初始化队列
        _dispatch_queue_init初始化队列,这里根据第一步规范后的参数判断设置最大并发数,DISPATCH_QUEUE_WIDTH_MAX从这里也可以知道最大的并发数是4094
        设置队列标识符等
      5. 赋值目标队列
        将上述设置的目标队列赋值 具体流程图如下:顺序时间轴.jpg
    • 队列和线程之间的关系图
    image.png
  • 函数源码分析

    • 异步函数分析
      • _dispatch_continuation_init任务包装函数
        主要是copy任务,赋值func、ctxt等参数 image.png 通过_dispatch_continuation_init_f函数赋值func、ctxt等参数 image.png
      • _dispatch_continuation_async并发处理函数
        image.png 全局搜索dx_push发现是个宏#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z),发现还是调用了dq_push方法继续全局搜索dq_pushiShot2021-04-08 09.15.36.png发现dq_push方法有很多个不过每个上面有类型不难猜测异步函数应该对应的是queue_concurrent下的方法对应的方法是_dispatch_lane_concurrent_push这里可以通过下符号断点来验证首先现在异步函数钱下个断点image.png然后再往下走发现断在了_dispatch_lane_concurrent_push的方法堆栈上所以也验证了上述猜想image.png,继续往下跟,全局搜索_dispatch_lane_concurrent_pushimage.png任务执行即出队所以一般并行队列中的异步任务的话都是空的所以如果不添加栅栏函数则一般会执行_dispatch_continuation_redirect_push方法这里也可以通过添加符号断点来验证image.pngimage.pngimage.pngimage.png发现的确_dispatch_lane_concurrent_push方法执行完成之后直接进入到_dispatch_continuation_redirect_push方法然后就执行了任务继续全局搜索_dispatch_continuation_redirect_push方法image.png此时的dq_push对应的就不是_dispatch_lane_concurrent_push方法了因为对应的队列类型变成了跟队列,所以此时应该对应的是_dispatch_root_queue_push方法同样的也可以根据上述的步骤下一个符号断点来验证,继续全局搜索_dispatch_root_queue_push方法image.png下个_dispatch_root_queue_push_override符号断点在下一个_dispatch_root_queue_push_inline符号断点发现会走到_dispatch_root_queue_push_override方法中然后image.png里面又继续调用了_dispatch_root_queue_push_inline方法image.png继续查找_dispatch_root_queue_poke方法image.png全局搜索_dispatch_root_queue_poke_slow方法iShot2021-04-08 11.10.05.png到这里异步函数创建线程的流程就已经完成如下图未命名文件(29).jpg但是此时仅仅是创建了线程,但是任务还没有执行此时再看上方的_dispatch_root_queues_init方法点进去发现发现是dispatch_once_f方法调用image.png传入的func_dispatch_root_queues_init_onceiShot2021-04-08 14.40.24.png,继续进到_dispatch_worker_thread2(线程的执行是系统底层去出发的,这里只需要绑定线程,系统会去触发线程)方法查看源码image.png_dispatch_root_queue_drain方法源码image.pngimage.pngimage.png最终发现在这里调用了任务方法,分析之前完全可以打断点然后打印堆栈调用情况然后再去源码中找对应代码,堆栈调用情况如下:image.png发现堆栈的调用情况就和上述分析的流程一致,
        总流程图如下:未命名文件(30).jpg
    • 同步函数分析
      首先看dispatch_sync源码image.png继续看_dispatch_sync_f源码image.pngimage.png发现同步函数的地方就是通过栅栏函数来实现的(栅栏函数源码如下)
  • 栅栏函数源码分析

    • 同步栅栏函数必须自己的block任务执行完成,下面的任务block才会执行,这就表示同步阻塞的是当前的线程
    • 异步栅栏函数不需要等自己的任务block执行,下面的代码会接着执行,这就表示异步阻塞的是队列(queue)。 注意:栅栏函数只能用在并发自定义队列中,全局并发队列,如果堵塞了其他系统任务,会造成其他任务,串行队列使用栅栏函数等于没用
      dispatch_barrier_sync源码如下:image.pngimage.pngimage.pngimage.png 同步栅栏函数源码流程:未命名文件(31).jpg
  • 死锁源码分析

    上述栅栏函数分析得知死锁在_dispatch_sync_f_slow方法中处理的image.png首先将当前任务添加到队列中然后执行__DISPATCH_WAIT_FOR_QUEUE__方法image.png再看_dq_state_drain_locked_by方法源码image.pngimage.png判断dq是否为正在等待的队列,然后给出一个状态state,然后将dq的状态和当前任务依赖的队列进行匹配,匹配成功则抛出异常
  • 单列源码分析

    全局搜索dispatch_once查看源码image.png同时发现_dispatch_Block_invoke就是一个宏#define _dispatch_Block_invoke(bb) \ ((dispatch_function_t)((struct Block_layout *)bb)->invoke)就是invoke方法image.pngimage.pngimage.pngimage.pngimage.png 单例底层实现流程图未命名文件(31).jpg
  • 信号量

    控制最大并发量(一般使用不多)核心函数如下:
    1. dispatch_semaphore_create 创建信号量,指定最大并发数
    2. dispatch_semaphore_signal 发送信号
    3. dispatch_semaphore_wait 等待信号 代码示例如下:
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_semaphore_t sem = dispatch_semaphore_create(2);
        //任务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);
        });
    }
    
    打印结果如下:image.png可以发现因为控制最大的并发量是2所以能同时执行的任务有两个,此时任务一和任务2是同时执行的,任务三是等到前两个任务执行完成之后再执行的
    源码分析:
    1. dispatch_semaphore_create
      主要是初始化工作,设置最大并发量(最大并发量要大于0),封装成dispatch_semaphore_t结构体 image.png
    2. dispatch_semaphore_wait
      主要是对并发量进行--操作,如果当前的并发量已经小于0了说明当前已经达到了最大并发量,所以任务进行无限等待状态(阻塞线程)源码如下image.png os_atomic_dec2o是个宏,因为里面是一个一个宏嵌套,所以这里一个一个剖析后如下;
      os_atomic_dec2o(dsema, dsema_value, acquire);
      os_atomic_sub2o(dsema, dsema_value, 1, acquire)
      os_atomic_sub(&(dsema)->dsema_value, (1), acquire)
      _os_atomic_c11_op(&(dsema)->dsema_value, (1), acquire, sub, -)
      atomic_fetch_sub_explicit(&(dsema)->dsema_value, 1)
      等价于&(dsema)->dsema_value = &(dsema)->dsema_value -1
      
    3. dispatch_semaphore_signal
      dispatch_semaphore_wait相反,主要是对并发量进行加加操作 image.png
  • 调度组

    调度组主要的作用就是监听某些任务执行状态,比如现在需要刷新UI但是需要两个请求完成之后再刷新UI,此时就可以使用调度组,可以等待两个异步任务完成之后收到完成通知后再刷新UI 核心函数:
    • dispatch_group_create 创建组
    • dispatch_group_async 进组任务
    • dispatch_group_notify 等待任务执行完成通知
    • dispatch_group_wait 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行
    • dispatch_group_enter标志着一个任务追加到 group,执行一次,相当于group 中未执行完毕任务数+1
    • dispatch_group_leave标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
      注意:dispatch_group_leavedispatch_group_enter需要成对出现
      使用示例:
    1. 正常情况示例
        
      
      打印情况: image.png
    2. 使用dispatch_group_wait
       dispatch_group_t group = dispatch_group_create();
       dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           sleep(1);
           NSLog(@"任务1执行完成");
           dispatch_group_leave(group);
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           NSLog(@"任务2执行完成");
           dispatch_group_leave(group);
       });
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           sleep(3);
           NSLog(@"任务3执行完成");
       });
       dispatch_group_notify(group, dispatch_get_main_queue(), ^{
           NSLog(@"任务12执行完成");
       });
      
       NSLog(@"任务6执行完成(主线程)");
      
      打印结果: image.png
    3. 特殊情况1(将通知放在最前面)
       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(@"任务12执行完成");
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           sleep(1);
           NSLog(@"任务1执行完成");
           dispatch_group_leave(group);
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           NSLog(@"任务2执行完成");
           dispatch_group_leave(group);
       });
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           sleep(3);
           NSLog(@"任务3执行完成");
       });    
      
       NSLog(@"任务6执行完成(主线程)");
      
      打印结果: image.png 说明通知放在最前面只要执行dispatch_group_leave就会执行通知
    4. 特殊情况2(多一个)dispatch_group_enter
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"任务1执行完成");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"任务2执行完成");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"任务3执行完成");
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"任务12执行完成");
        });
        NSLog(@"任务6执行完成(主线程)");
      
      打印情况: image.png 发现dispatch_group_notify一直都不会执行
    5. 特殊情况2(多一个)dispatch_group_leave image.png 直接会崩溃,所以说dispatch_group_leavedispatch_group_enter一定要成对出现 源码探索:
    • dispatch_group_create
      image.png image.png
    • dispatch_group_enter
      image.png image.png image.png
    • dispatch_group_leave
      image.png image.png
    • dispatch_group_async
      相当于帮开发者省去了dispatch_group_enterdispatch_group_leave两个方法的调用 image.png image.png image.png 发现底层在创建之前调用了dispatch_group_enter方法 image.png 然后再任务执行完毕之后又调用了dispatch_group_leave
    • dispatch_group_notify
      image.png image.png
  • Dispatch Source

    在任⼀线程上调⽤它的的⼀个函数dispatch_source_merge_data后,会执⾏ Dispatch Source 事先定义好的句柄(可以把句柄简单理解为⼀个 block )这个过程叫Custom event,⽤户事件。是dispatch source⽀持处理的⼀种事件
    句柄是⼀种指向指针的指针 它指向的就是⼀个类或者结构,它和系统有很密切的关系 HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON (图标句柄)等。这当中还有⼀个通⽤的句柄,就是HANDLE
    核心方法:
    1. dispatch_source_create创建源
    2. dispatch_source_set_event_handler设置源事件回调
    3. dispatch_source_merge_data源事件设置数据
    4. dispatch_source_get_data获取源事件数据
    5. dispatch_resume继续
    6. dispatch_suspend挂起 使用示例:
     - (void)viewDidLoad {
         [super viewDidLoad];
    
    
         self.totalComplete = 0;
    
         self.queue = dispatch_queue_create("td.com", NULL);
    
         /* 
         DISPATCH_SOURCE_TYPE_DATA_ADD        自定义的事件,变量增加
         DISPATCH_SOURCE_TYPE_DATA_OR         自定义的事件,变量OR
         DISPATCH_SOURCE_TYPE_DATA_REPLACE    自定义的事件,变量Replace
         DISPATCH_SOURCE_TYPE_MACH_SEND       MACH端口发送    
         DISPATCH_SOURCE_TYPE_MACH_RECV       MACH端口接收 
         DISPATCH_SOURCE_TYPE_MEMORYPRESSURE  内存报警
         DISPATCH_SOURCE_TYPE_PROC            进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
         DISPATCH_SOURCE_TYPE_READ            IO操作,如对文件的操作、socket操作的读响应
         DISPATCH_SOURCE_TYPE_SIGNAL          接收到UNIX信号时响应
         DISPATCH_SOURCE_TYPE_TIMER           定时器
         DISPATCH_SOURCE_TYPE_VNODE           文件状态监听,文件被删除、移动、重命名
         DISPATCH_SOURCE_TYPE_WRITE           IO操作,如对文件的操作、socket操作的写响应
         DISPATCH_MACH_SEND_DEAD
         */
         self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
         dispatch_source_set_event_handler(self.source, ^{
    
             NSLog(@"%@",[NSThread currentThread]);
    
             NSUInteger value = dispatch_source_get_data(self.source);
             self.totalComplete += value;
             NSLog(@"进度: %.2f",self.totalComplete/100.0);
             self.progressView.progress = self.totalComplete/100.0;
         });
    
         self.isRunning = YES;
         dispatch_resume(self.source);
     }
     - (IBAction)didClickStartOrPauseAction:(id)sender {
    
         if (self.isRunning) {
             dispatch_suspend(self.source);
             dispatch_suspend(self.queue);
             NSLog(@"已经暂停");
             self.isRunning = NO;
             [sender setTitle:@"暂停中.." forState:UIControlStateNormal];
         }else{
             dispatch_resume(self.source);
             dispatch_resume(self.queue);
             NSLog(@"已经执行了");
             self.isRunning = YES;
             [sender setTitle:@"暂停中.." forState:UIControlStateNormal];
         }
     }
    
     - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
         NSLog(@"开始了");
    
         for (int i= 0; i<100; i++) {
             dispatch_async(self.queue, ^{
                 if (!self.isRunning) {
                     NSLog(@"已经暂停");
                     return;
                 }
                 sleep(1);
                 dispatch_source_merge_data(self.source, 1);
             });
         }
    
     }