Swift3.0 gcd学习(1)
最近机缘巧合花了点时间研究了下IOS的多线程,这里做个总结,算是学习笔记,不足之处,还请大家多指正。废话不多,这就开始!
demo git地址
多线程概述
IOS多线程的方案,基本有四条路:
- gcd
- NSOperation
- NSThread
- Pthreads
说起IOS多线程解决方案,gcd基本是第一选择。不过让oc程序员比较纠结的事情是:到底是直接用gcd,还是用基于gcd做了面向对象封装的NSOperation。不过到了swift3.0,也就不用纠结了,苹果把gcd的调用完全对象化(就这一种调用方式,没得选)。一言不合上代码:
DispatchQueue.main.async {
print("Hello gcd");
}
gcd概要
gcd,全名Grand Central Dispatch,是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式。下面先来梳理一些基本的术语(部分内容来自网络)
Serial vs. Concurrent 串行 vs. 并发
任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行。
Synchronous vs. Asynchronous 同步 vs. 异步
在 GCD 中,一个同步函数只在完成了它预定的任务后才返回,因此会阻塞当前进程。
一个异步函数刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。
Critical Section 临界区
就是一段不能被并发执行代码,或者说,两个线程不能同时执行这段代码。例如一段代码(或者说block)去修改一个非线程安全的对象。
Race Condition 竞态条件
指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。
Deadlock 死锁
所谓的死锁是指两个线程因为互相等待对方完成而都被卡住。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成(之后会详细介绍)
Thread Safe 线程安全
线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。
Context Switch 上下文切换
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。
Concurrency vs Parallelism 并发与并行
并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别。并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉。
接下来,介绍一些gcd的重要概念
队列(dispatch queue)
- 主队列:运行在主线程中的串行队列。
- 全局队列:可以并发的执行多个任务,有多个优先级,但执行完成顺序是随机的。
- 自建队列:可以设置串行还是并行,也可以设置优先级。
//获取主队列
let mainQueue = DispatchQueue.main;
//获取一个全局队列
let defaultGlobal = DispatchQueue.global(qos: DispatchQoS.QoSClass.default);
//自建一个队列
let q = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil);
类型
串行:队列中任务逐个运行,在队列中永远只有一个任务在运行。主队列是串行队列。
并行:队列可以并发的执行多个任务,但执行完成顺序是随机的。全局队列就是一个并行队列。
自建队列也可以在构造时通过attributes参数设置队列是串行还是并行
DispatchQueue.Attributes.initiallyInactive(串行,默认值)
DispatchQueue.Attributes.concurrent(并行)
优先级
userInteractive:UI相关,交互等。
userInitiated:用户发起需要马上得到结果进行后续任务。
utility:花费时间稍多比如下载,需要几秒或几分钟的。
background:不可见在后台的操作可能需要好几分钟甚至几小时的。
default:默认的不应该使用这个设置任务
可以通过qos参数获取特定优先级的全局队列:
//获得一个后台优先级的全局队列
let defaultGlobal = DispatchQueue.global(qos: DispatchQoS.QoSClass.background);`
自建队列构造时也可以通过qos参数设置对列的优先级:
let q = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil);
阻塞与非阻塞运行
DispatchQueue队列对象有多种运行方式:
非阻塞运行async
所谓非阻塞运行,即队列的运行并不会阻塞当前线程,一言不合上代码:
let g = DispatchQueue.global();
print("1");
g.async {
//模拟大量运算
Thread.sleep(forTimeInterval: TimeInterval(exactly: 3.0)!);
print("2");
}
print("3");
//运行结果
1
3
(3秒之后)
2
不管队列里运行的block消耗多少时间,队列运行都会立刻返回,并不会阻塞之后代码的运行。
阻塞运行sync
阻塞运行的示例:
let g = DispatchQueue.global();
print("1");
g.sync {
Thread.sleep(forTimeInterval: TimeInterval(exactly: 3.0)!);
print("2");
}
print("3");
//运行结果
1
(3秒之后)
2
3
全局队列g采用了阻塞运行,所以队列g之后的代码必须要等队列内的block执行完才能运行
延迟运行:asyncAfter
延时提交block,不是延时立刻执行
let g = DispatchQueue.global();
print("1");
g.asyncAfter(deadline: DispatchTime.now() + 3.0) {
print("2");
}
print("3");
//运行结果
1
3
(3秒之后)
2
group
gourd也是gcd常用的一个功能:
let g = DispatchGroup();
let globalQueue = DispatchQueue.global();
let workItemA = DispatchWorkItem()
{
for i in 0...3
{
print("workItemA block: ", i);
}
}
let workItemB = DispatchWorkItem()
{
for i in 0...3
{
print("workItemB block: ", i);
}
}
globalQueue.async(group: g, execute: workItemA);
globalQueue.async(group: g, execute: workItemB);
// 通知方式
// g.notify(queue: DispatchQueue.main)
// {
// print("group is done");
// }
// 等待方式
// g.wait();
// print("group is done");
值得注意的是当group里所有block都完成后的通知方式,第一种是g.wait();
,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用g.notify
,异步执行闭包,不会阻塞。