对 iOS 多线程技术 GCD、NSOperation、NSTread的一些总结。
GCD替我们做了哪些工作(为什么要使用GCD)
基本概念
它是一个在
线程池模式的基础上执行的并发任务。线程池的限制上线是64个
为什么要用 GCD
- GCD可用于
多核的并行运算 - GCD会
自动利用更多的CPU内核(比如双核、四核) - GCD会
自动管理线程的生命周期(创建线程、调度任务、销毁线程) - 工程师只需要告诉GCD想要执行什么任务,
不需要编写任何线程管理代码
核心概念
任务和队列
任务执行任务有两种方式:
同步执行(sync)和异步执行(async)主要区别:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。- 同步执行:等待/只能在
当前线程中执行任务,不具备开启新线程的能力 - 异步执行:不等待/可以在
新的线程中执行任务,具备开启新线程的能力
- 同步执行:等待/只能在
队列这里的队列指
执行任务的等待队列,即用来存放任务的队列采用FIFO(先进先出)的原则有两种队列:串行队列和并发队列- 串行队列:只开启
一个线程,一个任务执行完毕后,再执行下一个任务SERIAL - 并发队列:可以开启
多个线程,并且同时执行任务CONCURRENT并发队列的并发功能只有在异步(dispatch_async)函数下才有效
- 串行队列:只开启
使用步骤使用步骤其实很简单,只有两步1.
创建一个队列(串行队列或并发队列)2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)队列的创建方法/获取方法可以使用dispatch_queue_create来创建队列,需要传入两个参数- 第一个参数:队列的
唯一标识符 - 第二个参数:
识别是串行队列还是并发队列 - 串行队列:
主队列(Main Dispatch Queue)主线程执行 - 并发队列:GCD
默认提供了全局并发队列(Global Dispatch Queue)- 需要传入两个参数:1.表示
队列优先级,一般默认 2.没什么用,一般用0
- 需要传入两个参数:1.表示
- 第一个参数:队列的
任务的创建方法同步执行任务的创建方法:dispatch_sync异步执行任务创建方法:dispatch_async队列&任务
组合方式- 1.同步执行 + 并发队列
- 2.异步执行 + 并发队列
- 3.同步执行 + 串行队列
- 4.异步执行 + 串行队列
- 5.同步执行 + 主队列
- 6.异步执行 + 主队列
GCD 线程间的
通信其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作GCD 的
其他方法栅栏方法:dispatch_barrier_async并发队列在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作延时执行方法:dispatch_after主队列时间不精确一次性代码(只执行一次):dispatch_once保证线程安全且只执行一次快速迭代:dispatch_apply指定的次数for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个元素队列组:dispatch_group全局并发队列notify返回指定线程执行任务,一般是主线程信号量:dispatch_semaphore持有计数的信号,计数为0时等待,计数为1或大于1时,计数减1并执行- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为
线程加锁
GCD在后台执行一个任务,小任务定时器,实现步骤:
获取全局并发队列dispatch_get_global_queue- 将获取到的全局并发队列作为参数,
创建队列dispatch_source_create 设置定时器dispatch_source_set_timer设置响应分派源事件的block,在分派源指定的队列上运行dispatch_source_set_event_handler开始执行派发源dispatch_resume
实现一个GCD的线程池
一个简单线程池至少包含下列组成部分:
线程池管理器(ThreadPoolManager):用于创建并管理线程池线程池管理器至少有下列功创建线程池销毁线程池添加新任务
工作线程(WorkThread): 线程池中线程一个可以循环执行任务的线程,在没有任务时将等待任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等任务队列:用于存放没有处理的任务。提供一种缓冲机制优化:- 动态增加工作线程
- 优化工作线程数目
写出GCD的执行结果
1 | // 题目:写出NSLog的打印结果(来自美团 GCD 面试题) |
NSOpration 和 NSOperationQueue
简介
NSOperation和NSOperationQueue是基于GCD的更高一层的封装,分别对应GCD的任务和队列,完全地面向对象。
但是比GCD更简单易用、代码可读性也更高。NSOperation和NSOperationQueue对比GCD会带来一点额外的系统开销,但是可以在多个操作Operation中添加附属。
GCD与NSOpration的区别
- 设置依赖关系
- 设置监听进度
- 设置优先级
- 还能继承
- 可以取消准备执行的任务
- 比GCD会带来一点额外的系统开销
- 比GCD更简单易用、代码可读性也更高
如何使用
可以通过
start方法直接启动NSOperation子类对象,并且默认同步执行任务,将NSOperation子类对象添加到NSOperationQueue中,该队列默认并发的调度任务。
开启操作的方式
开启操作有二种方式,一是通过start方法直接启动操作,该操作默认同步执行,二是将操作添加到NSOperationQueue中,然后由系统从队列中获取操作然后添加到一个新线程中执行,这些操作默认并发执行。
具体实现
- 方式一:直接由NSOperation子类对象启动。 首先将需要执行的操作封装到NSOperation子类对象中,然后该对象调用Start方法。
- 方式二:当添加到NSOperationQueue对象中,由该队列对象启动操作。
- 将需要执行的操作封装到NSOperation子类对象中
- 将该对象添加到NSOperationQueue中
- 系统将NSOperation子类对象从NSOperationQueue中取出
- 将取出的操作放到一个新线程中执行
使用队列来执行操作,分为2个阶段:第一阶段:添加到线程队列的过程,是上面的步骤1和2。第二阶段:系统自动从队列中取出线程,并且自动放到线程中执行,是上面的步骤3和4。
NSOperation
NSOperation是一个和任务相关的抽象类,不具备封装操作的能力,必须使用其子类。
NSOperation⼦类的方式有3种:
- 系统实现的具体子类:NSInvocationOperation
- 系统实现的具体子类:NSBlockOperation
- 自定义子类,实现内部相应的⽅法。该类是线程安全的,不必管理线程生命周期和同步等问题。
NSInvocationOperation子类
NSInvocationOperation类是NSOperation的一个具体子类,管理作为调用指定的单个封装任务执行的操作。这个类实现了一个非并发操作。方法属性无论使用该子类的哪个在初始化的方法,都会在添加一个任务。 和NSBlockOperation子类不同的是,因为没有额外添加任务的方法,使用NSInvocationOperation创建的对象只会有一个任务。
创建操作对象的方式
- 使用
initWithTarget:selector:object:创建sel参数是一个或0个的操作对象 - 使用
initWithInvocation:方法,添加sel参数是0个或多个操作对象。
在未添加到队列的情况下,创建操作对象的过程中不会开辟线程,会在当前线程中执行同步操作。创建完成后,直接调用start方法,会启动操作对象来执行,或者添加到NSOperationQueue队列中。
默认情况下,调用start方法不会开辟一个新线程去执行操作,而是在当前线程同步执行任务。只有将其放到一个NSOperationQueue中,才会异步执行操作。
NSBlockOperation子类
NSBlockOperation类是NSOperation的一个具体子类,它管理一个或多个块的并发执行。可以使用此对象一次执行多个块,而不必为每个块创建单独的操作对象。当执行多个块时,只有当所有块都完成执行时,才认为操作本身已经完成。
添加到操作中的块(block)将以默认优先级分配到适当的工作队列。
创建操作对象的方式
- 可以通过
blockOperationWithBlock:创建NSBlockOperation对象,在创建的时候也添加一个任务。如果想添加更多的任务,可以使用addExecutionBlock:方法。 - 也可以通过
init:创建NSBlockOperation对象。但是这种创建方式并不会在创建对象的时候添加任务,同样可以使用addExecutionBlock:方法添加任务。
对于启动操作和NSInvocationOperation类一样,都可以通过调用start方法和添加NSOperationQueue中来执行操作。
自定义子类
一般类NSInvocationOperation、NSBlockOperation就可以满足使用需要,当然还可以自己自定义子类。
创建的子类时,需要考虑到可能会添加到串行和并发队列的不同情况,需要重写不同的方法。对于串行操作,仅仅需要重新main方法就行,在这个方法中添加想要实现的功能。对于并发操作,重写四个方法:start、asynchronous、executing、finished。并且需要自己创建自动释放池,因为异步操作无法访问主线程的自动释放池。
注意:在自定义子类时,经常通过cancelled属性检查方法是否取消,并且对取消的做出响应。
线程安全
在NSOperation实例在多线程上执行是安全的,不需要添加额外的锁。
NSThread
NSThread是 Apple 官方提供面向对象操作线程的技术。有点事简单方便,可以直接操作线程对象,缺点是需要自己手动控制线程的生命周期。
NSThread 能做的事情几乎都可以被 GCD 或 NSOperation 代替,使用场景较少,所以只做概念上的了解即可。
基本属性
线程字典
threadDictionary ,每个线程都维护了一个 key-value 的字典,它可以在线程里面的任何地方被访问。 你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。 比如,你可以使用它来存储在你的整个线程过程中 Run loop 里面多次迭代的状态信息。
线程优先级
threadPriority ,NSThread 可以手动设置线程的优先级。
线程名称
name ,NSThread 可以手动设置每个线程的名称。
栈大小
stackSize ,NSThread 可以手动设置线程使用栈区大小,默认是512K。
线程执行
NSThread 可以手动取消、结束正在执行的线程,以及判断线程的当前执行状态。