参考资料:
iOS多线程编程总结
iOS多线程详解
使用GCD
iOS 並行程式設計: 初探 NSOperation 和 Dispatch Queues
基本概念
队列:是一种数据结构,它以先进先出(FIFO)的方式来管理存储的对象。
concurrent 并发:指的是多任务同时发生,需要被同时处理的这一现象。
parallelism 并行:指的是同时处理多个任务的技术。
并发是一中现象,为了处理这种现象,通过并发技术创建多线程来解决并发问题。
串行:与并行相反,表示任务按顺序一个一个执行。
fun1()
fun2()
同步:必须等 fun1 执行完才能执行 fun2;
异步:不需要等fun1 执行完即可执行fun2。
GCD和NSOperation是两种实现并行编程的API。多线程是并行技术的运用,多线程使多个CPU同时工作成为可能。
GCD
Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法。
GCD 能够极大地方便开发者进行多线程编程。
GCD的使用核心
确定任务在哪个线程中,以什么样的顺序执行。
将要执行的任务/代码用block封装好,添加到合适的队列,并设置合适的执行方式。队列.执行方式.block。
GCD中队列和执行方式是同时出现的两个重要“参数”
队列 串行队列:一次只能执行一个任务。 并行队列:只能保证任务一开始的顺序是它们加入队列的顺序,它们执行时都是并行的,不能保证它们的执行时间和执行结束完成时间,任务的执行由系统决定。
执行方式: 同步:没有开线程的能力; 异步:有开线程的能力,根据具体队列决定是否开线程。 派发给串行队列和并发队列的异步任务都是在另外一个线程中执行的,即与创建队列的线程不同的线程。
队列\执行方式 | 同步 | 异步 | 对比 |
---|---|---|---|
并发队列 | 当前线程顺序执行 | 一个或多个新线程无序执行 | 同全局队列的区别:</br>并发队列有名称,在MRC下需要调用dispatch_release进行释放; </br>dispatch_barrier必须使用自定以的并发队列:开发第三方框架建议使用并发队列。 |
全局队列 | 当前线程顺序执行 | 新线程无序执行 | 同并发队列的区别:</br>全局队列没有名称,无论是ARC还是MRC都不需要考虑内存释放,日常开发中建议使用。 |
串行队列 | 当前线程顺序执行 | 一个新线程顺序执行 | |
主队列 | 在主线程中向主队列中添加同步任务,会造成线程死锁;</br>在其他线程中向主队列中添加同步任务,会在主线程中顺序执行 | 主线程顺序执行 |
死锁
上面提到在主线程中,向主队列中同步派发一个任务,就会造成死锁;实际上,只要向当前串行队列中同步派发一个任务,会造成死锁。
// eg: 1
override func viewDidLoad() {
super.viewDidLoad()
// 已经在主队列中,再向主队列中同步派发一个任务,造成死锁
DispatchQueue.main.sync {
print("123")
}
}
// eg: 2
override func viewDidLoad() {
super.viewDidLoad()
let serialQueue = DispatchQueue(label: "aSerialQueue")
serialQueue.async { // 这里无论是同步还是异步,其里面包裹的代码都是在serialQueue队列里同步执行
// 任务 1
// 片段1
print("1")
// 已经在串行队列serialQueue里,再向串行队列serialQueue中添加同步任务,就会造成死锁
// 片段2 sync表示同步的执行任务,也就是说执行sync后当前队列会阻塞
serialQueue.sync {
// 任务 2
print("2")
}
}
}
任务执行完成就需要片段2执行完成,片段2要执行完成的就需要任务2执行完成,而任务2执行完成就必须等任务1执行完成,即需要片段2执行了完成,这就形成可相互等待,导致死锁。
NSOperation
与GCD不同,Operation队列不需要遵从先进先出的原则,两者的不同之处:
- 不遵从先进先出的原则:在Operation中,你可以为任务设定执行的优先级并为任务之间添加依赖性。也就是说你可以让一些任务在其他任务执行完之后再执行。这就是它们不需要遵从先进先出原则的原因。
- 默认以并行的方式执行,你无法让任务以串行的方式执行,当然,可以通过设置任务之间的依赖方式,让Operation以某种顺序执行任务。
提交给Operation队列中的任务必须封装在NSOperation对象中。
NSOperation
封装了需要执行的操作和执行操作所需要的数据,可以以并发或者非并发的方式执行操作,NSOperation
本身是抽象基类,所以只能使用它的子类。
系统给我们提供了两个NSOperation
的子类:
NSBlockOperation
: 以block的方式创建操作,可以包含多个block,只有当所有的block都执行完成才视做该任务完成;
NSInvocationOperation
: 这个类创建出的NSOperation用于执行指定对象的选择器(selector)。
当然我们也可以自定义。
自定义NSOperation
的字类时需要注意:
自定义非并发的
NSOperation
,只需要实现两个方法:自定义初始化方法和main方法。自定义并发的
NSOperation
有一些必须实现的方法和属性:|方法/属性|描述| |:---:|---| |start|必须的,所有并发执行的
operation
都必须要重写这个方法,替换掉NSOperation
类中的默认实现。start
方法是一个operation
的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的start
方法中一定不要调用父类的实现[super start]
。| | main | 可选的,通常这个方法就是专门用来实现与该operation
相关联的任务的。尽管我们可以直接在start
方法中执行我们的任务,但是用main
方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使operation
的结构更清晰;| |isExecuting 和 isFinish|必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化。| | isConcurrent |必须的,这个方法的返回值用来标识一个operation
是否是并发的operation
,我们需要重写这个方法并返回YES
。|
NSOperation的使用:
- 执行操作:
NSOperation
调用-(void)start
方法即可开始执行操作,默认按同步方式执行,即在调用start
方法所在的线程中执行。NSOperation
的-(Bool)isConcurrent
方法会返回当前操作相对于调用start方法的线程是同步还是异步执行。默认返回NO,异步执行。 - 取消操作:
NSOperation
开始执行操作之后默认会一直执行操作直到完成,可以调用-(void)cancel
方法中途取消操作。需要注意的是,调用cancel
方法后并不是立即取消的,而是在下一个isCancelled
的检查点取消的。 - 执行完成:如果想在
NSOperation
执行操作完成之后做一些处理,可以调用setCompletionBlock
方法来在block
中处理。
三种多线程技术比较
1、NSThread
优点:NSThread 比其他两个轻量级,使用简单
缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销
2、GCD GCD 是iOS 4.0以后才出现的并发技术
使用方式:将任务添加到队列(串行/并行(全局)),指定执行任务的方法(同步(阻塞)/异步 ) 拿到主队列:dispatch_get_main_queu() NSOperation无法做到的:1.一次性执行,2.延迟执行,3.调度组(op实现要复杂的多 )
3、NSOperation
NSOperation iOS2.0的时候就出现了(当时不好用,后来苹果对其进行改造)
使用方式:将操作(异步执行)添加到队列(并发/全局) 拿到主队列:[NSOperationQueue mainQueue] 主队列,任务添加到主队列就会在主线程执行 提供了GCD不好实现的:1.最大并发数,2.暂停和继续,3.取消所有任务,4.依赖关系