[toc]
背景简介
在初学iOS相关知识过程中,大多都对多线程有些恐惧的心里,同时感觉工作中用上的概率不大。但是如果平时不多积累并学透多线程,当工作中真的需要用到的时候,就很可能简单百度后把一些知识点稀里糊涂地就用到工作中了,殊不知里面有很多的坑,也有很多技巧需要在理论上先做了解,再结合实战,进一步去体会多线程的魅力和强大。
接下来,就对多线程来源的背景进行简单的介绍:
在计算的早期,计算机可以执行的最大工作量是由 CPU 的时钟速度决定的。但是随着技术的进步和处理器设计的紧凑化,热量和其他物理约束开始限制处理器的最大时钟速度。因此,芯片制造商寻找其他方法来提高芯片的总体性能。他们决定的解决方案是增加每个芯片上的处理器核心数量。通过增加内核的数量,一个单独的芯片可以每秒执行更多的指令,而不用增加 CPU 的速度或改变芯片的大小或热特性。唯一的问题是如何利用额外的内核。
应用程序使用多核的传统方法是创建多个线程。与依赖线程不同,iOS 采用异步设计方法来解决并发问题。通常,这项工作涉及获取一个后台线程,在该线程上启动所需的任务,然后在任务完成时向调用方发送通知(通常通过一个回调函数)。
iOS 提供了一些技术,允许您异步执行任何任务,而无需自己管理线程。异步启动任务的技术之一是 Grand Central Dispatch (GCD)。这种技术采用线程管理代码,并将该代码移动到系统级别。您所要做的就是定义要执行的任务,并将它们添加到适当的分派队列中。GCD 负责创建所需的线程,并安排任务在这些线程上运行。由于线程管理现在是系统的一部分,GCD 提供了任务管理和执行的整体方法,比传统线程提供了更高的效率。
OperationQueue(操作队列,api 类名为 NSOperationQueue )是 Objective-C 对象,是对 GCD 的封装。其作用非常类似于分派队列。您定义要执行的任务,然后将它们添加到 OperationQueue 中, OperationQueue 处理这些任务的调度和执行。与 GCD 一样, OperationQueue 为您处理所有线程管理,确保在系统上尽可能快速有效地执行任务。
接下来,就对现在工作中常用的这两种技术进行比较和实例解析。
GCD、OperationQueue 对比
核心理念
- GCD的核心概念:将 任务(block) 添加到队列,并且指定执行任务的函数。
- NSOperation 的核心概念:把 操作(异步) 添加到 队列。
区别
GCD:
- 将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
- GCD是底层的C语言构成的API
- iOS 4.0 推出的,针对多核处理器的并发技术
- 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
- 要停止已经加入 queue 的 block 需要写复杂的代码
- 需要通过 Barrier(dispatch_barrier_async)或者同步任务设置任务之间的依赖关系
- 只能设置队列的优先级
- 高级功能: dispatch_once_t(一次性执行, 多线程安全); dispatch_after(延迟); dispatch_group(调度组); dispatch_semaphore(信号量); dispatch_apply(优化顺序不敏感大体量for循环);
OperationQueue:
OC 框架,更加面向对象,是对 GCD 的封装。
iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层进行了全部重写。
可以设置队列中每一个操作的 QOS() 队列的整体 QOS
操作相关 Operation作为一个对象,为我们提供了更多的选择: 任务依赖(addDependency),可以跨队列设置操作的依赖关系; 在队列中的优先级(queuePriority) 服务质量(qualityOfService, iOS8+); 完成回调(void (^completionBlock)(void)
队列相关 服务质量(qualityOfService, iOS8+); 最大并发操作数(maxConcurrentOperationCount),GCD 不易实现; 暂停/继续(suspended); 取消所有操作(cancelAllOperations); KVO 监听队列任务执行进度(progress, iOS13+);
接下来通过文字,结合实践代码(工程链接在文末)和运行效果 gif 图对部分功能进行分析。
GCD
队列
串行队列(Serial Queues)
串行队列中的任务按顺序执行;但是不同串行队列间没有任何约束; 多个串行队列同时执行时,不同队列中任务执行是并发的效果。比如:火车站买票可以有多个卖票口,但是每个排的队都是串行队列,整体并发,单线串行。
注意防坑:串行队列创建的位置。比如下面代码示例中:在for循环内部创建时,每个循环都是创建一个新的串行队列,里面只装一个任务,多个串行队列,结果整体上是并发的效果。想要串行效果,必须在for循环外部创建串行队列。
串行队列适合管理共享资源。保证了顺序访问,杜绝了资源竞争。
代码示例:
private func serialExcuteByGCD(){
let lArr : [UIImageView] = [imageView1, imageView2, imageView3, imageView4]
//串行队列,异步执行时,只开一个子线程
let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
for i in 0..<lArr.count{
let lImgV = lArr[i]
//清空旧图片
lImgV.image = nil
//注意,防坑:串行队列创建的位置,在这创建时,每个循环都是一个新的串行队列,里面只装一个任务,多个串行队列,整体上是并行的效果。
// let serialQ = DispatchQueue.init(label: "com.companyName.serial.downImage")
serialQ.async {
print("第\(i)个 开始,%@",Thread.current)
Downloader.downloadImageWithURLStr(urlStr: imageURLs[i]) { (img) in
let lImgV = lArr[i]
print("第\(i)个 结束")
DispatchQueue.main.async {
print("第\(i)个 切到主线程更新图片")
lImgV.image = img
}