OC底层原理25-GCD分析下

457 阅读7分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

一.栅栏函数的应用

1.并发栅栏函数异步

- (void)concurrent_barrierAsync
{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_async
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
}

运行打印结果

Xnip2021-08-14_15-17-14.jpg 2.并发栅栏函数同步

- (void)concurrent_barrierSync
{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
    
}

运行打印结果

Xnip2021-08-14_15-18-21.jpg

3.全局队列栅栏函数异步

- (void)global_barrierAsync
{
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        sleep(1.5);
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_async
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        sleep(0.5);
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
}

运行打印结果 并发无序 不可使用全局队列

Xnip2021-08-14_15-22-29.jpg 4.全局队列栅栏函数同步

- (void)global_barrierSync
{
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
    
}

运行打印结果 并发1 2执行完 [NSThread currentThread] 4 3

Xnip2021-08-14_15-29-50.jpg

5.串行栅栏函数异步

- (void)serial_barrierAsync
{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_async
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
}

运行打印结果 4无序 1 2 [NSThread currentThread] 3有序

Xnip2021-08-14_15-38-35.jpg

6.串行栅栏函数同步

- (void)serial_barrierSync
{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
    
}

运行打印结果

Xnip2021-08-14_15-40-31.jpg

二.栅栏函数的底层原理

为什么栅栏函数可以堵塞下面的代码 并且控制流程 下面我们探究一下原理 为什么可以使用自定义并发队列 而不能使用全局队列

1.dispatch_barrier_sync

Xnip2021-08-14_16-09-45.jpg

进入 _dispatch_barrier_sync_f

Xnip2021-08-14_16-11-08.jpg

进入 _dispatch_barrier_sync_f_inline

Xnip2021-08-14_16-12-03.jpg

先进入_dispatch_sync_recurse

Xnip2021-08-14_16-18-11.jpg

要想dispatch_barrier_sync的block回调 必须的把dispatch_barrier_sync前面的任务全部执行完 在执行block 意味着当前队列无任务

接下来我们看_dispatch_sync_invoke_and_complete_recurse

Xnip2021-08-14_16-22-16.jpg

接下来我们进入 _dispatch_sync_complete_recurse

Xnip2021-08-14_16-28-56.jpg

进入dx_wakeup->dq_wakeup

Xnip2021-08-14_16-31-07.jpg 串行和并发 dq_wakeup->_dispatch_lane_wakeup 全局队列dq_wakeup->_dispatch_root_queue_wakeup

进入 _dispatch_lane_wakeup

Xnip2021-08-14_16-35-55.jpg 进入 _dispatch_lane_barrier_complete 通过_dispatch_lane_drain_non_barriers这个把barriers干掉

Xnip2021-08-14_16-39-00.jpg

进入 _dispatch_lane_class_barrier_complete 所有状态清空

Xnip2021-08-14_16-42-11.jpg

全局队列dq_wakeup->_dispatch_root_queue_wakeup 这里面没有对栅栏处理

Xnip2021-08-14_16-50-33.jpg

2.dispatch_barrier_async

Xnip2021-08-14_16-58-49.jpg 进入_dispatch_continuation_async

Xnip2021-08-14_16-59-28.jpg

进入dx_push->dq_push

Xnip2021-08-14_17-00-41.jpg

并发进入 _dispatch_lane_concurrent_push

Xnip2021-08-14_17-01-25.jpgbarrier 进入 _dispatch_lane_push

Xnip2021-08-14_17-02-14.jpg 然后走同步的流程 进入dx_wakeup->dq_wakeup

三.信号量使用

  • dispatch_semaphore_create 创建信号量
  • dispatch_semaphore_wait 信号量等待
  • dispatch_semaphore_signal 信号量释放
  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");
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    
    //任务2
    dispatch_async(queue, ^{
        sleep(2);
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);

        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });
    
打印结果
2021-08-14 17:13:51.790864+0800 006---GCD最大并发数[37192:1201311] 执行任务1
2021-08-14 17:13:51.791029+0800 006---GCD最大并发数[37192:1201311] 任务1完成
2021-08-14 17:13:52.793637+0800 006---GCD最大并发数[37192:1201313] 执行任务3
2021-08-14 17:13:52.794132+0800 006---GCD最大并发数[37192:1201313] 任务3完成
2021-08-14 17:13:53.794370+0800 006---GCD最大并发数[37192:1201309] 执行任务2
2021-08-14 17:13:53.794940+0800 006---GCD最大并发数[37192:1201309] 任务2完成

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); // 等待
        sleep(0.5);
        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    //任务2
    dispatch_async(queue, ^{
        sleep(0.5);
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(0.5);

        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });
    //任务4
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(0.5);
        NSLog(@"执行任务4");
        NSLog(@"任务4完成");
        dispatch_semaphore_signal(sem);
    });
打印结果
2021-08-14 17:16:37.592382+0800 006---GCD最大并发数[37213:1203700] 执行任务1
2021-08-14 17:16:37.592394+0800 006---GCD最大并发数[37213:1203701] 执行任务3
2021-08-14 17:16:37.592519+0800 006---GCD最大并发数[37213:1203701] 任务3完成
2021-08-14 17:16:37.592519+0800 006---GCD最大并发数[37213:1203700] 任务1完成
2021-08-14 17:16:37.592673+0800 006---GCD最大并发数[37213:1203702] 执行任务2
2021-08-14 17:16:37.592699+0800 006---GCD最大并发数[37213:1203704] 执行任务4
2021-08-14 17:16:37.592799+0800 006---GCD最大并发数[37213:1203702] 任务2完成
2021-08-14 17:16:37.592839+0800 006---GCD最大并发数[37213:1203704] 任务4完成

dispatch_semaphore_create控制最大并发数

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
    sleep(3);
    NSLog(@"执行任务1");
    NSLog(@"任务1完成");
});

dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"执行任务2");
    NSLog(@"任务2完成");
    dispatch_semaphore_signal(sem); // 发信号
});
打印结果
2021-08-14 20:58:18.699219+0800 006---GCD最大并发数[37635:1299035] 执行任务2
2021-08-14 20:58:18.699519+0800 006---GCD最大并发数[37635:1299035] 任务2完成
2021-08-14 20:58:21.700031+0800 006---GCD最大并发数[37635:1299030] 执行任务1
2021-08-14 20:58:21.700493+0800 006---GCD最大并发数[37635:1299030] 任务1完成

dispatch_semaphore_wait等待 执行dispatch_semaphore_signal发信号才能执行任务1 达到一个控制流程的效果

四.信号量原理

dispatch_semaphore_wait等待 dispatch_semaphore_signal发信号 底层到底做了什么事情

1.dispatch_semaphore_wait

Xnip2021-08-14_22-01-52.jpg 如果创建的dispatch_semaphore_t sem = dispatch_semaphore_create(0)

0-1 = -1 直接走 _dispatch_semaphore_wait_slow

Xnip2021-08-14_22-04-12.jpg 进入 _dispatch_sema4_wait

Xnip2021-08-14_22-04-52.jpg ret == -1 就一直dowhile循环卡死这里

所以应该先执行另外的线程 先执行dispatch_semaphore_signal 在执行dispatch_semaphore_wait 如果创建的dispatch_semaphore_t sem = dispatch_semaphore_create(0)

Xnip2021-08-14_22-06-24.jpg 0+1 =1 直接return 然后执行dispatch_semaphore_wait 1-1 = 0 直接return

五.调度组的应用问题

  • dispatch_group_create  创建组
  • dispatch_group_async  进组任务
  • dispatch_group_notify 进组任务执行完毕通知
  • dispatch_group_wait 进组任务执行等待时间
  • dispatch_group_enter 进组
  • dispatch_group_leave 出组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
    //创建调度组
    [self.mArray addObject:@"1"];
});
dispatch_group_async(group, queue, ^{
    [self.mArray addObject:@"2"];
    
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
   NSLog(@"数组=%@",self.mArray);
});
打印结果
2021-08-14 22:37:22.112900+0800 005---GCD进阶使用(下)[38331:1372252] 数组=(
    1,
    2
)
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
    //创建调度组
    [self.mArray addObject:@"1"];
});
dispatch_group_async(group, queue, ^{
    [self.mArray addObject:@"2"];
    
});
// 进组和出组 成对  先进后出
dispatch_group_enter(group);
dispatch_async(queue, ^{
    [self.mArray addObject:@"3"];
    dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
   NSLog(@"数组=%@",self.mArray);
});
打印结果
2021-08-14 22:40:15.258917+0800 005---GCD进阶使用(下)[38364:1375249] 数组=(
    1,
    2,
    3
)

六.调度组的原理

  • 进组和出组顺序颠倒为啥会崩溃
  • 为什么有同步的效果
  • dispatch_group_async = dispatch_group_enter + dispatch_group_leave

1.dispatch_group_create

Xnip2021-08-14_22-43-35.jpg

进入 _dispatch_group_create_with_count 跟信号量的初始化不同

Xnip2021-08-14_22-44-02.jpg

2.dispatch_group_enter

Xnip2021-08-14_22-54-43.jpg 0->-1 3.dispatch_group_leave

Xnip2021-08-14_22-56-08.jpg -1->0 唤醒dispatch_group_notify继续执行

4.dispatch_group_async

Xnip2021-08-14_22-58-40.jpg 进入 _dispatch_continuation_group_async

Xnip2021-08-14_22-59-21.jpg 进入 _dispatch_continuation_async

Xnip2021-08-14_23-00-22.jpg

进入dx_push->dq_push

Xnip2021-08-14_23-01-23.jpg

进入_dispatch_root_queue_push

Xnip2021-08-14_23-01-59.jpg 进入 _dispatch_root_queue_push_inline

Xnip2021-08-14_23-05-37.jpg

进入_dispatch_root_queue_poke

Xnip2021-08-14_23-06-01.jpg 进入 _dispatch_root_queue_poke_slow

Xnip2021-08-14_23-06-23.jpg 进入 _dispatch_root_queues_init

Xnip2021-08-14_23-06-56.jpg 进入 _dispatch_root_queues_init_once

Xnip2021-08-14_23-07-26.jpg

进入 _dispatch_worker_thread2

Xnip2021-08-14_23-07-57.jpg

进入 _dispatch_root_queue_drain

Xnip2021-08-14_23-08-49.jpg

进入_dispatch_continuation_pop_inline

Xnip2021-08-14_23-09-09.jpg

进入 _dispatch_continuation_invoke_inline

Xnip2021-08-14_23-10-04.jpg 进入 _dispatch_continuation_with_group_invoke

Xnip2021-08-14_23-11-29.jpg

七.dispatch_source

  • dispatch_source_create 创建源
  • dispatch_source_set_event_handler 设置源回调事件
  • dispatch_source_merge_data 源事件设置数据
  • dispatch_source_get_data 获取事件源数据
  • dispatch_resume 继续
  • dispatch_suspend 挂起
 __block int timeout=10; //倒计时时间
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
      dispatch_source_set_timer(self.timer ,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
      dispatch_source_set_event_handler(self.timer, ^{
          if(timeout<=0){ //倒计时结束,关闭
              dispatch_source_cancel(self.timer);
              dispatch_async(dispatch_get_main_queue(), ^{
                  //设置界面的按钮显示 根据自己需求设置
                  NSLog(@"timer 停止");
               
              });
          }else{
              int minutes = timeout / 60;
              int seconds = timeout % 60;
              NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新获取验证码",minutes, seconds];
              dispatch_async(dispatch_get_main_queue(), ^{
                  //设置界面的按钮显示 根据自己需求设置
                  NSLog(@"%@",strTime);
              });
              timeout--;
                   
          }
      });
      
     //恢复 启动
    dispatch_resume(self.timer);
    //挂起
    dispatch_suspend(self.timer);

八.可变数组不安全分析

  dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // 多线程 操作marray
    for (int i = 0; i<1000; 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];
            [self.mArray addObject:image];

        });
    }

运行打印结果发现崩溃 原因是线程不安全 self.mArray不断的读写 写的操作是把旧值release 新值retain 有可能一个线程正在release 另外一个线程正在读 读取的是野指针 同一时间对同一片内存空间进行操作 不安全 为了让其安全需要加锁 或者加dispatch_barrier_async

  dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // 多线程 操作marray
    for (int i = 0; i<1000; 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];
            dispatch_barrier_async(concurrentQueue , ^{
                [self.mArray addObject:image];
            });

        });
    }