ios 中的 GCD
摘自:http://www.cocoachina.com/swift/20150129/11057.html
libdispatch是Apple所提供的在IOS和OS X上进行并发编程的库,而GCD正是它市场化的名字。GCD有如下优点: – GCD可以将计算复杂的任务放到后台执行,从而提升app的响应性能 – GCD提供了比锁和线程更简单的并发模型,帮助开发者避免并发的bug。
为了理解GCD,你需要了解一些线程和并发的概念。这些概念可能很含糊并且细微,所以先简要回顾一下。
串行 vs 并发
这两个词用来描述任务的执行顺序。串行在同一时间点总是单独执行一个任务,而并发可以同时执行多个任务。
任务
在本教程中,你可以把任务当做一个闭包(closure)。实际上,你可以将GCD和函数指针一起使用,但是一般很少这样使用。闭包更简单!
不记得Swift中的闭包?闭包是自含的,可保存传递并被调用的代码块。当调用的时候,他们的用法很像函数,可以有参数和返回值。除此之外,闭包可以“捕获”外部的变量,也就是说,它可以看到并记住它自身被定义时的作用域变量。
Swift中的闭包和OC中的块(block)类似甚至于他们几乎就是可交换使用的。唯一的限制在于OC中不能使用Swift独有的特性,比如元组(tuple)。但OC中的块可以安全的替换成Swift中的闭包。
同步 vs 异步
这两个词描述的是函数何时将控制权返回给调用者,以及在返回时任务的完成情况。
同步函数只有在任务完成后才会返回。
异步函数会立即返回,不会等待任务完成。因此异步函数不会阻塞当前线程。
注意:当你读到同步函数阻塞(block)当前进程或者函数是阻塞(blocking)函数时,不要困惑!动词阻塞(block)描述的是函数对当前线程的影响,和块(block)没有关系。同时记住GCD文档中有关OC的block可以跟Swift的闭包互换。
临界区(Critical Section)
这是一段不能并发执行的代码,也就是说两个线程不可以同时执行它。这通常是因为这段代码会修改共享的资源。否则,并发的进程同时修改同一个变量会导致错误。
竞态条件
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。竞态条件可能产生在代码检查时不易被发现的不可预期行为。
死锁
两个或更多的线程因等待彼此完成而陷入的困境称为死锁。第一个线程无法完成因为它在等待第二个线程完成。但是第二个线程也无法完成因为它在等待第一个线程完成。
线程安全
线程安全的代码是可以被多个线程或并发任务安全调用的,他不会造成任何问题(数据错误, 崩溃等)。非线程安全的代码在同一时间只能单独执行。一段线程安全的代码如let a = ["thread-safe"]。由于数组是只读的,它可以被多个线程同时使用而不会引发问题。另一方面,var a = ["thread-unsafe"]是可变数组。这意味着它不是线程安全的,因为多个线程可以同时获取并修改这个数组,会得到不可预料的结果。非线程安全 的变量和可变的数据结构在同一时刻应该只能被一个线程获取。
上下文切换
上下文切换是在进程中切换不同线程时保存和恢复程序执行状态的过程。这一过程在编写多任务app时相当常见,但是会造成一些额外开支。
并发 vs 并行
并发和并行经常会被同时提起,所以值得通过简短的解释来区分彼此。
并发代码中的单独部分可以同时执行。然而,这要由系统来决定并发怎样发生或是否发生。
多核设备通过并行来同时执行多个线程;然而,在单核设备中,必须要通过上下文切换来运行另一个线程或进程。这一过程通常发生的很快以至于给人并行的假象。
队列
GCD提供了调度队列(dispatch queues)来处理提交的任务;这些队列管理着你向GCD提交的任务并且以先进先出(FIFO)的顺序来执行任务。这保证了第一个加入队列的任务第一个被执行,第二个加入的任务第二个开始执行,以此类推。
所有调度队列都是线程安全的从而让你可以同时在多个线程中使用它们。当你明白了调度队列如何为你的代码提供了线程安全性时,GCD的优点就很明显了。关键是选择正确的调度队列种类和正确的调度函数(dispatching function)来提交你的任务。
顺序队列
顺序队列中的任务同一时间只执行一件任务,每件任务只有在先前的任务完成后才开始。同时,你并不知道一个任务完成到另一个任务开始之间的间隔时间
任务的执行是在GCD掌控之下的;你唯一确定的就是GCD在同一时刻只执行一件任务并且按任务加入队列的顺序执行。
因为不会在顺序队列中同时执行两件任务,所以没有多个任务同时进入临界区的危险;这保证了临界区不会出现竞态条件。因此如果进入临界区的唯一途径就是通过向调度队列提交任务,那么可以保证临界区是安全的。
并发队列中的任务可以保证按进入队列的顺序被执行…仅此而已!任务可能以任意顺序完成而且你不知道何时下一个任务会开始,或是任一时刻有多少任务在运行。再一次,这完全取决于GCD。 下图展示了四个并发任务的例子:
何时开始一个任务完全取决于GCD。如果一个任务的执行时间和另一个的发生重叠,将由GCD来决定是否要将任务运行在另一个可用的核上或是通过上下文切换来运行另一个程序。
首先,系统提供了一种特殊的顺序队列main queue。和其他的顺序队列一样,在这个队列里的任务同一时刻只有一个在执行。然而,这个队列保证了所有任务会在主线程中执行,主线程是唯一一个允许更新UI的线程。这个队列用来向UIView对象发消息或发通知。
系统同时提供了几种并发队列。这些队列和它们自身的QoS等级相关。QoS等级表示了提交任务的意图,使得GCD可以决定如何制定优先级。
-
QOS_CLASS_USER_INTERACTIVE: user interactive等级表示任务需要被立即执行以提供好的用户体验。使用它来更新UI,响应事件以及需要低延时的小工作量任务。这个等级的工作总量应该保持较小规模。
-
QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起并且可以异步执行。它应该用在用户需要即时的结果同时又要求可以继续交互的任务。
-
QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,常常伴随有用户可见的进度指示器。使用它来做计算,I/O,网络,持续的数据填充等任务。这个等级被设计成节能的。
-
QOS_CLASS_BACKGROUND:background等级表示那些用户不会察觉的任务。使用它来执行预加载,维护或是其它不需用户交互和对时间不敏感的任务。