并发编程简单来说就是让CPU在同一时间运行多个任务. 这里面有几个容易混淆的概念:
几个概念
串行(Serial) VS. 并行(Concurrent)
串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.
同步(Synchronous) VS. 异步(Asynchronous)
同样的,同步和异步操作的主要区别在于是否等待操作执行完成,亦即是否阻塞当前线程。同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。
并发(Concurrency) VS. 并行(Parallelism)
并发是程序的属性(property of the program), 而并行是计算机的属性(property of the machine).
并行和并发都是用来让不同的任务可以"同时执行", 只是并发是伪同时, 而并行是真同时. 假设你有任务T1和任务T2(这里的任务可以是进程也可以是线程):
a. 首先如果你的CPU是单核的, 为了实现"同时"执行T1和T2, 那只能分时执行, CPU执行一会儿T1后马上再去执行T2, 切换的速度非常快(这里的切换也是需要消耗资源的, context switch), 以至于你以为T1和T2是同时执行了(但其实同一时刻只有一个任务占有着CPU).
b. 如果你是多核CPU, 那么恭喜你, 你可以真正同时执行T1和T2了, 在同一时刻CPU的核心core1执行着T1, 然后core2执行着T2.
其实我们平常说的并发编程包括狭义上的"并行"和"并发", 你不能保证你的代码会被并行执行, 但你可以以并发的方式设计你的代码. 系统会判断在某一个时刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)执行, 否则就用context switch来分时并发(concurrency)执行.
队列 vs. 线程
有一些对 iOS 并发编程模型不太了解的同学可能会对队列和线程产生混淆,在 iOS 中,有两种不同类型的队列,分别是串行队列和并发队列。正如我们上面所说的,串行队列一次只能执行一个任务,而并发队列则可以允许多个任务同时执行。iOS 系统就是使用这些队列来进行任务调度的,它会根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动地管理。
并发编程模型
在其他许多语言中,为了提高应用的并发性,我们往往需要自行创建一个或多个额外的线程,并且手动地管理这些线程的生命周期,这本身就已经是一项非常具有挑战性的任务了。此外,对于一个应用来说,最优的线程个数会随着系统当前的负载和低层硬件的情况发生动态变化。因此,一个单独的应用想要实现一套正确的多线程解决方案就变成了一件几乎不可能完成的事情。而更糟糕的是,线程的同步机制大幅度地增加了应用的复杂性,并且还存在着不一定能够提高应用性能的风险。
然而,值得庆幸的是,在 iOS 中,苹果采用了一种比传统的基于线程的系统更加异步的方式来执行并发任务。与直接创建线程的方式不同,我们只需定义好要调度的任务,然后让系统帮我们去执行这些任务就可以了。我们可以完全不需要关心线程的创建与销毁、以及多线程之间的同步等问题,苹果已经在系统层面帮我们处理好了,并且比我们手动地管理这些线程要高效得多。
因此,我们应该要听从苹果的劝告,珍爱生命,远离线程。不过话又说回来,尽管队列是执行并发任务的首先方式,但是毕竟它们也不是什么万能的灵丹妙药。所以,在以下三种场景下,我们还是应该直接使用线程的:
用线程以外的其他任何方式都不能实现我们的特定任务;
必须实时执行一个任务。因为虽然队列会尽可能快地执行我们提交的任务,但是并不能保证实时性;
你需要对在后台执行的任务有更多的可预测行为。
Operation Queues vs. Grand Central Dispatch (GCD)
简单来说,GCD 是苹果基于 C 语言开发的,一个用于多核编程的解决方案,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。而 Operation Queues 则是一个建立在 GCD 的基础之上的,面向对象的解决方案。它使用起来比 GCD 更加灵活,功能也更加强大。下面简单地介绍了 Operation Queues 和 GCD 各自的使用场景:
Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;
GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。
<ul>
<li>NSOperation</li>
</ul>
<ul>
<li>GCD (Grand Central Dispatch)</li>
</ul>
GCD VS NSOperation
GCD 的优点:
GCD 提供的 dispatch_after 支持调度下一个操作的开始时间而不是直接进入睡眠。
NSOperation 中没有类似 dispatch_source_t,dispatch_io,dispatch_data_t,dispatch_semaphore_t 等操作。
NSOperation 的优点:
GCD 没有操作依赖。我们可以让一个 Operation 依赖于另一个 Operation,这样的话尽管两个 Operation 处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
GCD 没有操作优先级(GCD 有队列优先级),能够使同一个并行队列中的任务区分先后地执行,而在 GCD 中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
GCD 没有 KVO。NSOperation 可以监听一个 Operation 是否完成或取消,这样能比GCD 更加有效地掌控我们执行的后台任务
在NSOperationQueue 中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而 GCD 没法停止已经加入 queue 的 Block(其实是有的,但需要许多复杂的代码)
我们能够对 NSOperation 进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将 block 任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。