[TOC]
GCD、NSOperation和多线程编程推荐阅读
五星:
四星:
多线程编程概念
基本概念、术语:
- 进程(Precess):进程是操作系统管理和分配资源的最小单位,每个进程都有自己的内存空间、系统资源,至少有一个主线程和多个辅助线程。在iOS中,每个App运行的时候,都有对应的进程。
- 线程(Thread):线程则是操作系统具体的执行单元,代码的执行是在线程来完成的。在iOS中,线程的底层实现是基于POSIX thread API的,也就是我们常说的pthread。
- 任务(Task):任务是我们抽象出来的需要执行的工作,一般指代一段代码。
- 同步 vs 异步
- 同步是指函数的调用会阻塞当前的线程,必须等待函数返回才能继续执行接下来的代码。
- 异步函数的调用则不会阻塞当前线程,函数调用之后立刻返回,一般通过回调函数来处理函数的执行结果。
- 异步函数能够有效的完成一些耗时的任务,而不必影响代码的执行流,能够提高代码的处理效率。
- 串行 vs 并发
- 串行指的是在同一个时间只能有一个任务在执行。
- 并发指的是在同一个时间可以有多个任务一起执行。
- 并发一般用于多核编程,通过高效的利用多核的优势,把不同的任务分配到各个执行单元来提高效率。
- Dispatch Queue vs Operation Queue
- GCD和NSOperation/NSOperationQueue是iOS上面来处理多线程开发的工具,对应的概念分别是Dispatch Queue和Operation Queue。
iOS并发编程模型
- 在其他的操作系统中,我们往往需要手动创建线程、管理线程的生命周期,在不需要的时候负责销毁线程和线程使用的资源,更加痛苦的是需要使用线程锁、信号量、代码临界区等手段完成线程的同步工作,这些操作往往容易出错而且繁杂。
- iOS通过抽象出队列的概念,让开发者更加关注于任务的安排和调度,而从线程的管理工作中解脱出来。在很多时候,iOS把一些繁杂且容易出错的工作(ARC代替MRC)抽离到底层中,能够让开发者把注意力更多地放到真正的任务上,这也是iOS能够吸引广大开发者的原因之一吧。
NSThread vs GCD vs NSOperation,它们到底是什么?
三种解决方案
- NSThread:一个封装pthread API的线程对象,需要进行线程创建、销毁和处理线程同步,是最接近系统底层的解决方案。
- GCD:苹果基于C语言开发的,一个用于多核编程的解决方案,是一个轻量级的、以FIFO的顺序来执行并发任务的库。
- NSOperation:建立在GCD的基础上,面向对象的解决方案,比GCD更加灵活,也更加强大。
它们具体是什么?
- NSThread: Cocoa对于pthread API的封装,提供了一套面向对象的接口,需要开发者自行管理线程的生命周期、处理线程同步。大多是的时候,我们是不需要直接使用这些底层的对象,而是使用GCD或者NSOperation等更加高级的接口。
- 关于Operation对象
- NSOperation对象本身是一个抽象类,不能直接使用。要么使用系统预定义的两个子类NSInvocationOperation和NSBlockOperation或者定义它的子类。
- NSInvocationOperation:可以使用object和selector来创建一个NSInvocationOperation,非常的方便和灵活。当代码中已经有相关的处理逻辑方法时,建议直接使用NSInvocationOperation来进行替代。
- NSBlockOperation:可以使用NSBlockOperation来并发的执行一个或者多个block,只有当所有的block都执行完毕,NSBlockOperation才算执行完成,有点像dispatch_group的概念。
- 所有的Operation都有下面的特性:
- 支持在 operation 之间建立依赖关系,只有当一个 operation 所依赖的所有 operation 都执行完成时,这个 operation 才能开始执行;
- 支持一个可选的 completion block ,这个 block 将会在 operation 的主任务执行完成时被调用,在任务被取消的时候也会执行;
- 支持通过 KVO 来观察 operation 执行状态的变化,Operation正是通过KVO通知来实现依赖运行,所以我们需要在自定义的子类中的处理好KVO的属性;
- 支持设置执行的优先级,从而影响 operation 之间的相对执行顺序;
- 支持取消操作,可以允许我们停止正在执行的 operation 。
- GCD队列:
- 以FIFO顺序执行任务的队列调度系统,先入队列的任务一定先执行。
- 两种类型的队列:
- 串行队列(Serial Queue):同一时间内只能有一个任务正在被执行。
- 并发队列(Concurrent Queue):同一时间内可以有多个任务同时被执行。
- iOS默认提供5个队列:
- 主队列(Main Queue):应用程序主线程应用的队列,用于更新UI,属于串行队列。
- 四个全局队列:
- 按照队列优先级排序,分别是:High、Default、Low、Background。
- 这四个全局队列是由系统提供的,在所有的App中共享,当然也包含了Apple的应用。
- 并发队列中任务的执行顺序:
- 由于队列的并发数是有系统根据当前的资源动态管理的,我们不知道也不能够设置队列中任务执行的时机和所需时长。
- 只有当位于队列前面的任务执行完毕、出队列后,才会执行后面的任务,但是当前并发执行的任务数量我们不得而知。
- 自定义队列:
- 我们可以自定义串行或者并发队列来完成任务的执行和调度。
各自的优势和劣势
- 优势:
- NSThread:
- 能够执行实时任务。其他两者都是由系统管理的队列,不能保证实时性。
- GCD:
- 可以非常简洁的完成简单异步任务的调用,如在主线程更新UI,延迟执行。
- 只需要把任务分发到队列之后,不需要管理任务的调度情况。
- NSOperation:
- 给任务添加依赖
- 取消或者暂停一个正在执行的任务
- 有一个可选的completionBlock
- 可以通过KVO来查看任务的执行情况
- 可以给任务设置优先级,从而影响任务的执行顺序
- NSThread:
- 短板:
- NSThread:需要进行线程的创建、销毁,以及处理线程同步的问题,过于繁杂,而且容易出错。
- GCD:
- 对于任务的管理不够:如不能取消任务、设置依赖和优先级等。
- NSOperation:
- 相比于GCD,会增加系统的额外开销。
多线程代码中,需要注意哪些问题?
- 有了GCD来调度block,我们为什么还需要NSBlockOperation?
- 现有代码已经在使用OperationQueue,而我们不想使用Dispatch Queue的时候,NSBlockOperation提供了一个面向对象的封装。
- 当我们需要dispatch queue不能提供的功能时,如KVO观察Operation状态变化、设置operation之间依赖等。
- NSInvocationOperation的灵活性是什么意思?
- 我们可以通过上下文来改变selector和object对象。
- 如何定义一个非并发的operation?
- 对于非并发的operation,我们只需要实现main方法和能够正常响应取消事件。
- 其他复杂的工作如KVO通知、依赖设置等工作NSOperation类的start方法已经帮我们提供了默认实现。
- 简单实现:
- 提供一个自定义的初始化方法
- 重写main方法
- 如何实现一个并发的operation?
- 一般我们不需要实现并发的operation,当operation和operation queue一起使用的时候,Operation Queue会为非并发的operation创建单独的线程。
- 默认情况下,operation是同步执行的,也就是我们直接使用start方法的时候,它是在调用者的线程中执行的。
- Operation的isConcurrent属性表明一个operation是否支持并发。
- 配置并发的Operation:只有当我们需要手动执行operation,并且希望支持并发执行,需要重写下面的方法。
- start:必须,配置任务执行的线程和其他资源,但是一定不能调用父类的实现。
- main:可选,一般用来执行具体的任务,而start方法更多是用来配置初始环境,当然也可以用来执行具体的任务。
- isExecuting和isFinished:必须,并发的Operation需要自己来配置环境,同时还需要向外界来传递状态的变化,而isExecuting和isFinished这两个状态的变化需要使用KVO来通知外部。
- isConcurrent:必须,用来标识一个Operation是否支持并发。
- 如何完成自定义operation的cancel操作?
- 在下面这几个关键点的检查isCancelled属性
- 在真正开始执行operaiton之前
- 至少在一次循环之中需要检查一次,如果单次循环耗时较长,则需要更加频繁的检查
- 在任何相对比较容易终止Operation的地方
- 需要注意的是虽然Operation支持取消操作,但是并不是立刻就可以被终止的,而是在下一个isCancelled的检查点。
- 在我们自定义Operation子类的时候,即使operation是被cancel了,我们仍然需要设置isFinished方法为true,因为在设置operation依赖的时候,它们的operation就是通过KVO来观察isFinished方法来判断时候可以执行的,如果在cancel的时候,没有设置isFinished方法,那么其他的operation将永远不会执行。
- 在下面这几个关键点的检查isCancelled属性
- 如何定制Operation对象的执行行为:
- 在Operation添加到Operation Queue之前,我们可以配置Operation的一些行为。
- 配置依赖关系
- 依赖关系是在Operation之间的,与是否在同一个Operation Queue没有关系,也就是说,我们在位于不同的Operation Queue中的Operation之间设置依赖。
- 注意不要产生依赖循环。
- 在把operation添加到Operation Queue之前就需要配置好依赖,在添加后设置的依赖可能无效。
- 修改operation在队列中的优先级
- Operation在队列中的执行顺序取决于isReady状态和队列优先级。
- isReady受它所依赖的operation状态的影响,只有当依赖的所有operation都变为isFinished的时候,isReady状态才为true。
- queuePriority只能作用与相同队列中的operation,并且队列优先级只有当isReady为true的时候,才会决定operation的执行顺序。
- isReady为FALSE的时候,isReady为true的低队列优先级的operation也会先执行。
- 一个串行的 operation queue 与一个串行的 dispatch queue是一样的么?
- 两者都是在同一时间内只能有一个任务被执行,但是在任务的执行顺序上是不同的:
- dispatch queue 的执行顺序一直是 FIFO 的
- operation queue中的operation执行的顺序取决于isReady和queuePriority状态。
- 两者都是在同一时间内只能有一个任务被执行,但是在任务的执行顺序上是不同的:
- 如何用GCD实现一个线程安全的单例?
- ObjC
- Swift