IMG_5727.JPG

barrier

栅栏函数

  • 最直接的作用:控制任务执行顺序,同步
  • 非常重要的一点:栅栏函数只能控制同一并发队列

    • dispatch_barrier_async 前面的任务执行完毕才会来到这里
    • dispatch_barrier_sync 作用相同,但是这个会阻塞线程,影响后面的任务执行

      举例1

  • 建立并发队列,异步执行任务,添加栅栏函数

  • 执行结果 e -> a -> b -> c -> d,barrier控制了并发队列,a、b一定在c前
  • 将栅栏函数队列改为concurrentQueue1,则不控制concurrentQueue队列,执行结果e -> d -> a -> c -> b ```cpp dispatch_queue_t concurrentQueue = dispatch_queue_create(“cooci”, DISPATCH_QUEUE_CONCURRENT);
    1. dispatch_queue_t concurrentQueue1 = dispatch_queue_create("kc", DISPATCH_QUEUE_CONCURRENT);

// dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0); // 这里是可以的额! / 1.异步函数 / dispatch_async(concurrentQueue, ^{ NSLog(@”a”); });

  1. dispatch_async(concurrentQueue, ^{
  2. sleep(1);
  3. NSLog(@"b");
  4. });
  5. /* 2. 栅栏函数 */ // - dispatch_barrier_sync
  6. dispatch_barrier_async(concurrentQueue, ^{
  7. NSLog(@"c");
  8. });
  9. /* 3. 异步函数 */
  10. dispatch_async(concurrentQueue, ^{
  11. NSLog(@"d");
  12. });
  13. // 4
  14. NSLog(@"e");
  1. - 将栅栏async改为sync,队列为concurrentQueuesync阻塞16后流程,执行结果为a -> b -> c -> d -> e
  2. ![路由器类图 (2).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1632130211524-7ed8e184-dcd0-418f-abaa-29f5784419fc.jpeg#clientId=u668a94bf-382a-4&from=ui&id=uff4ce834&margin=%5Bobject%20Object%5D&name=%E8%B7%AF%E7%94%B1%E5%99%A8%E7%B1%BB%E5%9B%BE%20%282%29.jpg&originHeight=624&originWidth=1561&originalType=binary&ratio=1&size=97876&status=done&style=none&taskId=u874047d3-58f2-4bc2-8ea0-266e1b86075)
  3. - **全局并发队列global_queue,添加barrier无效,全局并发队列不允许添加栅栏函数**
  4. <a name="colL0"></a>
  5. ## 举例2
  6. - 当数组操作未添加barrier隔离时,**赋值操作就是每次进行release旧值,retain新值**,在多线程操作下,可能某次release旧值未及时执行,导致下次连续两次release,再retain时**读取了野指针,**进而崩溃,出现读写异常。
  7. ```cpp
  8. dispatch_queue_t concurrentQueue = dispatch_queue_create("cc", DISPATCH_QUEUE_CONCURRENT);
  9. // dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
  10. // 多线程 操作marray
  11. for (int i = 0; i<1000; i++) {
  12. dispatch_async(concurrentQueue, ^{
  13. NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
  14. NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
  15. NSData *data = [NSData dataWithContentsOfURL:url];
  16. UIImage *image = [UIImage imageWithData:data];
  17. // self.mArray 多线程 地址永远是一个
  18. // self.mArray 0 - 1 - 2 变化
  19. // name = kc getter - setter (retain release)
  20. // self.mArray 读 - 写 self.mArray = newaRRAY (1 2)
  21. // 多线程 同时 写 1: (1,2) 2: (1,2,3) 3: (1,2,4)
  22. // 同一时间对同一片内存空间进行操作 不安全
  23. dispatch_barrier_async(concurrentQueue , ^{
  24. [self.mArray addObject:image];
  25. });
  26. });
  27. }

源码探索

  • 为什么可以同步?
  • 全局并发队列不能来?

    dispatch_barrier_sync

  • 流程调用

未命名文件 (1).jpg

  • _dispatch_barrier_sync_f_inline做了哪些事
    • 和dispatchsync底层一样,也会有dispatch_sync_f_slow判断,有可能出现死锁情况__DISPATCH_WAIT_FOR_QUEUE函数

image.png

  • _dispatch_sync_recurse做了哪些事
    • 底层是do while循环,不断的循环递归,相当于block() callout

image.png

  • _dispatch_sync_complete_recurse做了那些事

image.png

  • dx_wakeup宏定义,赋值不同
    • 如果是自定义队列,调用_dispatch_lane_wakeup

image.png
image.png

  • 如果是全局并发队列,调用_dispatch_root_queue_wakeup

image.png

总结

  • 底层是do while循环,会判断队列中的任务是否清空,同时将栅栏函数拔掉,没有任务再calloutBlock

    信号量

    ※以前调度组是封装了信号量,最新版本中则不是,自己又写了一套

  • dispatch_semaphore_create 创建信号量

  • dispatch_semaphore_wait 等待信号量
  • dispatch_semaphore_signal 信号量释放
  • 同步->当锁,控制GCD最大并发数

    举例3

  • 以下代码执行的打印结果为 执行任务1->执行任务2->任务1完成->任务2完成->执行任务3->执行任务4->任务3完成->任务4完成

  • 每次执行的任务并发量受信号量大小影响,例如,控制上传、下载数量
  • 信号量1代表允许通过1个,通过调整create数量,可以控制队列的最大并发数,应用 -> 一次下载多少个
  • 相当于同步加锁效果

    1. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    2. dispatch_semaphore_t sem = dispatch_semaphore_create(2);
    3. //任务1
    4. dispatch_async(queue, ^{
    5. dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
    6. sleep(2);
    7. NSLog(@"执行任务1");
    8. NSLog(@"任务1完成");
    9. });
    10. //任务2
    11. dispatch_async(queue, ^{
    12. dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    13. sleep(2);
    14. NSLog(@"执行任务2");
    15. NSLog(@"任务2完成");
    16. dispatch_semaphore_signal(sem); // 发信号
    17. });
    18. //任务3
    19. dispatch_async(queue, ^{
    20. dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    21. sleep(2);
    22. NSLog(@"执行任务3");
    23. NSLog(@"任务3完成");
    24. dispatch_semaphore_signal(sem);
    25. });
    26. //任务4
    27. dispatch_async(queue, ^{
    28. dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    29. sleep(2);
    30. NSLog(@"执行任务4");
    31. NSLog(@"任务4完成");
    32. dispatch_semaphore_signal(sem);
    33. });

    源码探索

  • dispatch_semaphore_wait对其sem减1,结果小于0则进入等待,起到一个同步的效果

  • 信号量就是对pthread的下层封装

    dispatch_semaphore_create

  • 传入的值大于等于0才有用,否则返回

    dispatch_semaphore_signal

  • 初始为0时,os_atomic_inc2o在原子的信号里进行++操作,如果大于0,则return掉,否则进入_dispatch_semaphore_signal_slow

image.png

  • _dispatch_semaphore_signal_slow,不断自增,返回1

image.png

dispatch_semaphore_wait

  • 底层本质是do while循环,进入了do while,后面代码就不会执行
  • 初始为0时,进行os_atomic_des2o原子—操作,结果小于0,进入_dispatch_semaphore_wait_slow等待,根据timeout判断等多久

image.png

  • _dispatch_semaphore_wait_slow等待,根据timeout判断等多久

image.png

补充

  • libdispatch是跨平台库,并不一定有machport,也不支持Mach,进支持posix的平台下,就没有mach port,用的是posix接口sem_wait等完成。sem本质是文件,操作由进程态切系统态后由系统保证文件操作的完整性。XNU下,也就是有Mach内核支持的时候,走的是Mach_sem,调用的是semaphore_wait,需要提供一个mach port完成port name到semaphore文件的转换,XNU是非实时系统,几乎所有需要切内核态的操作都是通过mach port来完成context,结果等的传递。posix下的比如BSD自然不需要mach port,本身semaphore就已经是个文件,不需要转那一步

    任务调度组

    源码探索

  • 底层dispatch_group_create和_dispatch_group_create_and_enter相似,区别只是传值count不同 ```cpp dispatch_group_create(void) { return _dispatch_group_create_with_count(0); }

_dispatch_group_create_and_enter(void) { return _dispatch_group_create_with_count(1); } ```

  • _dispatch_group_create_with_count做了什么

image.png

  • dispatch_group_enter做了什么 -> 类似信号量,0 -> -1减操作

image.png

  • dispatch_group_leave做了什么,-1 > 0 类似信号量singal加操作

image.png

  • _dispatch_group_notify做了什么,dispatch_async执行任务时是异步,此时dispatch_group_notify可能已经执行,所以要判断old_state是否为0,只有dispatch_group_leave执行了,将state变为0,才能触发notify的callback

image.png

总结

  • 调度组本质,enter时0变为-1,leave时-1变为0,调用group_wakeUp时,notify中判断oldValue是否为0,为0才执行block回调
  • notify中判断oldValue是否为0同时防止的异步线程的影响
  • dispatch_group_async相当于封装了dispatch_group_enter和dispatch_group_leave

    Dispatch_Source

  • 主要用途是timer计时器,GCD相当于对pthead封装,runloop与GCD同等级(没有归属)

  • 通过条件控制一个block执行,并且这个条件在不断变化
  • 其CPU负荷非常小,尽量不占用资源
  • 主要通过一个条件来控制任务是否执行(block执行),并且条件不断变化
  • 联合的优势
    • dispatch_source_create 创建源
    • dispatch_source_set_event_handler 设置源事件回调
    • dispatch_source_merge_data 源事件设置数据
    • dispatch_source_get_data 获取源事件数据
    • dispatch_resume 继续
    • dispatch_suspend 挂起
  • 不受runloop影响,底层是封装了pthread,是workLoop,定时器场合比较多

    可变数组线程安全问题

  • set方法-> 对新值retain,旧值release,本质写入不安全