对 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 可以手动取消、结束正在执行的线程,以及判断线程的当前执行状态。