GCD 学习总结

本篇内容主要总结自:
http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1
http://www.raywenderlich.com/79150/grand-central-dispatch-tutorial-swift-part-2
感谢原作者!

一些相关的名词。

  • 串行和并行

    用于描述队列。串行队列总是一个任务一个任务的执行,并行队列可能同时执行多个任务。

  • 同步和异步

    对函数调用来讲。同步调用会等到调用的方法执行完成再返回控制权,异步调用是通知函数去执行,然后不等待函数执行完成,即返回控制权。

  • 临界区

    临界区的代码一定不可以并行执行,他们通常是操作一个共享的数据。

  • 竞争条件

    两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序。

  • 死锁

    两个或多个线程都需要等待对方执行完成后再开始执行,导致互相等待,永远得不到执行的状况。

  • 线程安全

    线程安全的代码可以被多个线程或并行任务安全的执行而不会引发任何错误。非线程安全的代码在任意时间只能被一个线程执行。线程安全的一个例子是 let a = [“thread-safe”]。这个数组是只读的,你可以在同一时间从多个不同的线程访问他,不会有问题。然而,var a = [“thread-unsafe”] 中声明的这个数组是可以被改变的,这表明他不是线程安全的。因为如果多个线程在同一个时间给他赋值,其结果是不可预测的,和这些线程在那个时间点的执行顺序有关(竞争条件)。可以被改变的变量和数据结构都不是线程安全的,他们在某个时间点只能被一个线程访问,除非这个变量会数据结构在内部的线程安全的。

  • 上下文切换

    当要将时间片从当前线程切换到下一个线程的时候,需要保存当前线程的执行状态,然后加载下一个线程的执行状态。这个过程称为上下文切换。

  • 并发(Concurrency)和并行(Parallelism)

    多段代码被设置为并发执行,说明他们可以被同时执行。但是他们被同时执行的方式,甚至是到底能不能被同时执行,取决于运行的系统。

    多核心地设备通过并行(Parallelism)来同时运行多个线程。对于单核设备,他们需要切换时间片来模拟出一种同时执行的状态。

    尽管你使用 GCD 来设置你的代码并发执行,但是如何同时运行这些代码取决于 GCD。并行(Parallelism)一定是并发(Concurrency)的,并发(Concurrency)的却不一定是并行(Parallelism)的。

    更深层次的理解可以是:分辨出哪些代码可以并发执行,哪些不可以,实际是一个代码架构的问题。你在代码中写出来,表明你的代码架构如此,实际设备是不是同时执行的,就不是你能掌控的了。

队列

使用 GCD 就是选择合适的 dispatch 方法将你的任务提交到合适的队列中的过程,一旦你提交了,后面的事情GCD会帮你处理。所以第一,要选择 dispatch 方法,第二,要选择队列。这里先说队列。

GCD 中的所有队列都是线程安全的,有串行队列和并行队列两种。

串行队列中的任务一个一个的执行,前一个任务执行完成之后,后一个任务才会被执行。队列中任务的执行顺序就是他们被加入队列的顺序。

并行队列中的任务按照他们被加入到队列的顺序依次开始执行,这时唯一能够确定的。至于这些任务以怎样的顺序被执行完成,或者下一个任务什么时间能够开始,或者某个时间点有多少个任务在同时执行都无法预测。这些完全都是由 GCD 来控制的,你所要做的就是把任务提交到队列里去。

最常见的队列是主队列,也叫做 UI 队列,他是一个串行队列。这个队列中的任务都会在主线程中执行,主线程是唯一能够刷新UI界面和发送 notification 的线程。耗时任务不要在主线程中执行,会带来很差的用户体验。

GCD 还提供了四种全局使用的并行队列,使用这些队列的时候要传入一个与之关联的 QoS(Quality of Service) 类。这个 QoS 类帮助 GCD 确定将该任务添加到哪个优先级的队列中。QoS 类有四种:

  • QOS_CLASS_USER_INTERACTIVE
  • QOS_CLASS_USER_INITIATED
  • QOS_CLASS_UTILITY
  • QOS_CLASS_BACKGROUND

优先级从上到下依次降低。优先级高的队列将会优先获取系统资源来执行其中的任务。

此外,你还可以自定义串行或者并行队列。

常用方法

  • void dispatch_async( dispatch_queue_t queue, dispatch_block_t block)
  • void dispatch_sync( dispatch_queue_t queue, dispatch_block_t block)

    block 参数是你要提交的任务,queue 是你想要将任务提交到的队列。这个队列可以是同步的,也可以是异步的。

    方法名称中的 async 和 sync 是对于该方法的调用者而言的,表明调用过程是异步的或者同步的,也就是调用者不需要等待该方法执行完成,就可以继续向下执行(异步的话),或者必须等待任务执行完成才能继续向下执行(同步的话)。不能表明 block 中的任务的执行是异步的,这取决于你将该任务提交到了哪种队列中。串行队列中,则是同步执行的;并行队列中,就是异步执行的。

    注意:
    GCD 中常用的几个方法,都仅仅是将任务提交到队列中,任务最终是否被异步执行,取决于接收任务的队列。
    
    使用 dispatch_sync 方法时要注意不能将任务提交到当前所在的队列中,这会造成死锁,导致程序无法继续执行。因为队列要要执行完当前任务才能执行提交的新任务,当前任务执行完成需要等待提交的新任务执行完成;而提交的新任务在队列中,需要等待当前任务执行完成才能得以执行。这样互相等待,形成死锁。
  • void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)

    when 是延迟时间,queue 是要提交的队列,work 是你提交的任务。

    这个方法相当于是:等待 when 时间之后,调用 dispatch_async 将 work 提交到 queue 中。

    所以这个方法做计时是不准确的。因为 when 时间之后,只是将任务加入到了队列中。至于任务要多久才能执行完成,就不得而知了。

  • void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block)

    block 是要提交的任务,predicate 可以理解为 block 的任务的标示符。

    该方法中的 block 在程序运行期间,最多执行一次。predicate 帮助系统判断该 block 是否被执行过了,所以predicate 应该具有全局作用域。如果 predicate 不具有全局作用域,可能出现一些无法预测的结果。

    如果有多个线程同时想要执行这个block,第一个到达的线程可以执行,后面到达的线程则等待。当第一个线程执行完成之后,block 已经被执行过一次了,系统将会取消之前等待的线程的该任务。

    单例模式中单例的初始化常用这个方法。Swift 中常量的初始化,其实也是用的这个方法。

  • void dispatch_barrier_async( dispatch_queue_t queue, dispatch_block_t block)

  • void dispatch_barrier_sync( dispatch_queue_t queue, dispatch_block_t block)

    sync 和 async 的意思如上所讲。

    barrier 主要用来解决 Readers/Writers 问题。通过这两个方法加入到队列中的任务,有这样的特点:

    1. 必须等待队列中排在其之前的任务都执行完成才能执行,
    2. 队列中排在其后的任务必须在他执行完成之后才能执行。

    不论其所在队列是串行队列还是并行队列。根据这两个特点,也可以看出,使用这两个方法将任务提交到串行队列中是毫无意义的,因为串行队列中的任何一个任务都具有以上两个特点。所以这两个方法多用于向并行队列中提交任务。下面一张图说明其执行过程:

  • dispatch_group 系列方法

    如果有多个任务在同时执行,他们各自的执行时间都是不可预知的,而你需要知道这几个任务全部执行完毕的时间,从而进行后续的操作。这时,你可以将这几个任务都加入到一个 dispatch_group 中,当全部任务执行完毕后,dispatch_group 会给你发送通知。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值