iOS 中 GCD 的一些“常识”

我们使用多线程大多数的情况下都只有一个目的: 将耗时的操作放在后台工作,待耗时操作完成后更新 UI

在 iOS 开发中关于多线程的解决方案虽然有四种,但是实际上我们使用的最多的就两种: 一种是 C 语言的 GCD ; 另一种就是 Objective-C 语言的 NSOperation

另外两种(pthread 和 NSThread)或者由于生命周期需要手动管理,或者由于使用难度不利于开发等等原因,我们实际开发中基本是很少去用到

GCD:

  • GCD是苹果为多核的并行运算提出的解决方案,它的优点是会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉它干什么就行。
  • 它使用了 Block(Swift 里面叫做闭包),使用起来比较方便,所以基本上大家都会使用这套方案
GCD的核心
将任务添加到队列
  • 任务: 执行的操作
  • 队列: 用来存放任务
GCD使用的两个步骤:
  • 创建任务:确定要做的事情
  • 将任务添加到队列中
    • GCD会自动将队列中的任务取出,放到对应的线程中执行
GCD任务和队列
  • 队列:
    • 串行队列
      • 让任务一个接着一个有序的执行
      • 同时只能调度一个任务执行
      • 放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
    • 并发队列
      • 可以让多个任务同时执行,自动开启多个线程同时执行多个任务
      • 同时可以调度多个任务执行
      • 并发队列的并发功能只有内部的任务是异步执行,才有效
      • 放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
  • 任务:
    • 同步任务(sync)
      • 在当前线程中依次执行任务
      • 如果是 同步操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
    • 异步任务(async)
      • 新开线程,在新线程中执行任务
      • 如果是 异步操作,当前线程会直接往下执行,它不会阻塞当前线程。
创建队列
  • 主队列:这是一个特殊的串行队列。它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。
 dispatch_queue_t queue = dispatch_get_main_queue();
  • 自定义队列:自己可以创建 串行队列, 也可以创建 并行队列。它有两个参数,其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。
 //串行队列
  dispatch_queue_t queue = dispatch_queue_create("yl", NULL);
  dispatch_queue_t queue = dispatch_queue_create("yl", DISPATCH_QUEUE_SERIAL);
  //并行队列
  dispatch_queue_t queue = dispatch_queue_create("yl", DISPATCH_QUEUE_CONCURRENT);
  • 全局(并发)队列:这是系统提供的一个并发队列。第一个参数是设置队列优先级(实际上也并不能决定什么),第二个参数目前没有深层次的意义,一般两个参数都设为0
  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
创建任务
  • 同步任务:依次执行任务
dispatch_sync(queue, ^{
      // 这里写代码
  });
  • 异步任务:新开线程,在新线程中执行任务
dispatch_async(queue, ^{
      // 这里写代码
  });
GCD中队列和任务的搭配
  • 串行队列+异步任务
    • 开一条新线程:因为队列是顺序调度任务,前一个任务执行完成以后才能调度后面的任务
    • 按顺序执行
dispatch_async(dispatch_queue_create("yl", NULL), ^{
      // 这里写代码
  });
  • 并发队列+异步任务
    • 开启多条新线程
    • 无序执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 这里写代码
  });
  • 主队列+异步任务
    • 没有开启新线程(回到主线程)
    • 按顺序执行
dispatch_async(dispatch_get_main_queue(), ^{
      // 这里写代码
  });
当然,Block具有可以嵌套的特性,可以组合成很多种运用方式。
  • 比如:使用主队列+同步任务 嵌套 并发+异步任务(主队列+同步任务需要在外层),能够使主队列中的任务开启新线程依次执行

注意:在主队列中执行同步任务内部没有异步任务会造成死锁。 原因是主队列会等待同步任务执行完,同步任务会等待主队列调度,产生相互等待,造成线程死锁

GCD的高级功能
  • 延迟操作:指定时间间隔多少秒后执行内部代码块,dispatch_after 这个函数默认是异步执行的
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#延迟多少秒#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

   // 这里写代码
    });
  • 一次性执行: 代码只会执行一次,并且线程是安全的。 典型的应用场景就是单例设计模式
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

    // 只执行1次的代码(这里面默认是线程安全的)

});
  • 阻塞(Barrier):使用dispatch_barrier_async 添加的block会在之前添加的block全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作。
    • 注意: dispatch_barrier_async)必须使用自定义队列,否则执行效果和全局队列一致
dispatch_queue_t queue = dispatch_queue_create("yl", NULL);

dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 111
  });
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 222
  });
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 333
  });
dispatch_barrier_async(queue, ^{

      // 444
  });
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 555
  });
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 666
  });
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 777
  });

// 打印结果: 111 333 222 444 666 777 555
// 在 barrier 之后的 block会等到 barrier 及前面的代码执行完毕再执行
调度组:监听一组异步任务是否执行结束,如果执行结束就能够得到统一的通知
dispatch_group_t group = dispatch_group_create();  

// 某个任务放进 group  
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{  
    // 任务代码1  
});  
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{  
    // 任务代码2  
});  
dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
    // 任务全部完成处理    
});  
注意: 如果使用调度组异步任务内部嵌套处理异步任务会造成调度组无法监听任务执行完成,需要使用底层的 dispatch_group_enter(group); 与 dispatch_group_leave(group); 来监听 group 内部执行完毕的讯息
dispatch_group_t group = dispatch_group_create();  

// 某个任务放进 group  
dispatch_group_enter(group);  // 代码块开始执行
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{  
    // 任务代码1  

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 555
      dispatch_group_leave(group); // 代码块执行完毕
  });

});  

dispatch_group_enter(group);  // 代码块开始执行
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{  
    // 任务代码2  

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 555
      dispatch_group_leave(group); // 代码块执行完毕
  });

});  
dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
    // 任务全部完成处理    
});  

以上这些都是实际开发中比较常用的,当然还有一些GCD的其他用法:比如定时器 dispatch_time_t 与 dispatch_source_t ;还有比如 dispatch_apply 重复执行等等等等。

关于 NSOperation 与 GCD的选择,我觉得差别都不大,主要的区别就在于一些高级功能实现的难易程度,比如要设置队列的最大并发数,或者设置暂停/继续/取消等,那么还是 NSOperation 要来的容易一点。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值