并发所描述的概念就是同时运行多个任务。这些任务可能是以在单核 CPU 上分时(时间共享)的形式同时运行,也可能是在多核 CPU 上以真正的并行方式来运行。
OS X 和 iOS 提供了几种不同的 API 来支持并发编程。每一个 API 都具有不同的功能和使用限制,这使它们适合不同的任务。同时,这些 API 处在不同的抽象层级上。我们有可能用其进行非常深入底层的操作,但是这也意味着背负起将任务进行良好处理的巨大责任。
线程(thread)是组成进程的子单元,操作系统的调度器可以对线程进行单独的调度。实际上,所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)。
多线程可以在单核 CPU 上同时(或者至少看作同时)运行。操作系统将小的时间片分配给每一个线程,这样就能够让用户感觉到有多个任务在同时进行。如果 CPU 是多核的,那么线程就可以真正的以并发方式被执行,从而减少了完成某项操作所需要的总时间。
开发者可以使用 POSIX 线程 API,或者 Objective-C 中提供的对该 API 的封装 NSThread,来创建自己的线程。
使用 pthread 还是 NSThread 来直接对线程操作,都是相对糟糕的编程体验,这种方式并不适合我们以写出良好代码为目标的编码精神。
直接使用线程可能会引发的一个问题是,如果你的代码和所基于的框架代码都创建自己的线程时,那么活动的线程数量有可能以指数级增长。这在大型工程中是一个常见问题。例如,在 8 核 CPU 中,你创建了 8 个线程来完全发挥 CPU 性能。然而在这些线程中你的代码所调用的框架代码也做了同样事情(因为它并不知道你已经创建的这些线程),这样会很快产生成成百上千的线程。代码的每个部分自身都没有问题,然而最后却还是导致了问题。使用线程并不是没有代价的,每个线程都会消耗一些内存和内核资源。
接下来,我们将介绍两个基于队列的并发编程 API :GCD 和 operation queue 。它们通过集中管理一个被大家协同使用的线程池,来解决上面遇到的问题。
为了让开发者更加容易的使用设备上的多核CPU,苹果在 OS X 10.6 和 iOS 4 中引入了 Grand Central Dispatch(GCD)。
通过 GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理。这样可以将开发者从线程管理的工作中解放出来,通过集中的管理线程,来缓解大量线程被创建的问题。
GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。
GCD 队列的内部使用的是线程。GCD 管理这些线程,并且使用 GCD 的时候,你不需要自己创建线程。但是重要的外在部分 GCD 会呈现给你,也就是用户 API,一个很大不同的抽象层级。当使用 GCD 来完成并发的工作时,你不必考虑线程方面的问题,取而代之的,只需考虑队列和功能点(提交给队列的 block)。虽然往下深究,依然都是线程,但是 GCD 的抽象层级为你惯用的编码提供了更好的方式。
队列和功能点同时解决了一个连续不断的扇出的问题:如果我们直接使用线程,并且想要做一些并发的事情,我们很可能将我们的工作分成 100 个小的功能点,然后基于可用的 CPU 内核数量来创建线程,假设是 8。我们把这些功能点送到这 8 个线程中。当我们处理这些功能点时,可能会调用一些函数作为功能的一部分。写那个函数的人也想要使用并发,因此当你调用这个函数的时候,这个函数也会创建 8 个线程。现在,你有了 8 × 8 = 64 个线程,尽管你只有 8 个CPU内核——也就是说任何时候只有12%的线程实际在运行而另外88%的线程什么事情都没做。使用 GCD 你就不会遇到这种问题,当系统关闭 CPU 内核以省电时,GCD 甚至能够相应地调整线程数量。
操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。从本质上来看,操作队列的性能比 GCD 要低那么一点,不过,大多数情况下这点负面影响可以忽略不计,操作队列是并发编程的首选工具。
资料来源:《底层并发 API》和 《并发编程:API 及挑战》