什么是GCD
- Grand Central Dispatch 是异步执行任务的技术之一。开发者只需要定义想要执行的任务,并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并执行任务。我们看个经典的例子。
DispatchQueue.global().async {
/*
这里执行耗时操作
*/
var sum = 0
for i in 0...100 {
print("global线程:\(Thread.current)")
sum += i
}
/*
这里刷新UI
*/
DispatchQueue.main.async {
print("main线程:\(Thread.current)")
print(sum)
}
}
- Cocoa 框架提供了NSObject类的方法
self.performSelector(inBackground: #selector(doWork), with: nil)
func doWork() {
print("后台处理中")
self.performSelector(onMainThread: #selector(finishDone), with: nil, waitUntilDone: true)
}
func finishDone() {
print("后台处理完成")
}
多线程编程
我们知道代码基本上都是从上到下顺序执行代码。如果我们现在有个耗时的操作,那我们不想程序就卡在这里,我们就需要使用异步去处理,去处理下面的问题。或者我们通常添加一个progress提示用户正在处理某些东西。
队列(串行队列,并行队列)
串行队列:单个线程,等待上个任务执行完毕,才往后执行
并行队列:多个线程,可以同时执行
如其名称所示,是执行处理等待的队列。
两个关键队列 main, global
主队列,和全局队列。我们通常将我们要执行的操作,追加到这个队列后。async, sync 来说明这个block是异步还是同步执行。测试main, 和global
DispatchQueue.main.async {
print("当前线程\(Thread.current)------->1")
}
DispatchQueue.main.async {
print("当前线程\(Thread.current)------->2")
}
DispatchQueue.main.async {
print("当前线程\(Thread.current)------->3")
}
DispatchQueue.main.async {
print("当前线程\(Thread.current)------->4")
}
-打印结果(main队列只有一个线程所以是串行队列)
- 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->1
- 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->2
- 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->3
- 当前线程<NSThread: 0x7b257510>{number = 1, name = main}------->4
- 测试global队列
DispatchQueue.global().async {
print("当前线程\(Thread.current)------->1")
}
DispatchQueue.global().async {
print("当前线程\(Thread.current)------->2")
}
DispatchQueue.global().async {
print("当前线程\(Thread.current)------->3")
}
DispatchQueue.global().async {
print("当前线程\(Thread.current)------->4")
}
- 打印结果(global队列多个线程执行,并行执行是并行队列)
- 当前线程<NSThread: 0x7d160c10>{number = 3, name = (null)}------->3
- 当前线程<NSThread: 0x7bfa9dd0>{number = 2, name = (null)}------->1
- 当前线程<NSThread: 0x7d15de60>{number = 4, name = (null)}------->4
- 当前线程<NSThread: 0x7d153b90>{number = 5, name = (null)}------->2
如何创建队列
当然除了系统提供给我们的这两个队列,我们还可以自己创建队列。
串行队列
let queue = DispatchQueue(label: "myQueue")
queue.async {
print("当前线程\(Thread.current)------->1")
}
queue.async {
print("当前线程\(Thread.current)------->2")
}
queue.async {
print("当前线程\(Thread.current)------->3")
}
queue.async {
print("当前线程\(Thread.current)------->4")
}
// 打印结果
1. 当前线程<NSThread:0x7c0825f0>{number = 2, name = (null)}------->1
2. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->2
3. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->3
4. 当前线程<NSThread: 0x7c0825f0>{number = 2, name = (null)}------->4
- 并行队列
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
- qos 队列的优先等级
- User Interactive 和用户交互相关,比如动画等等优先级最高。比如用户连续拖拽的计算
- User Initiated 需要立刻的结果,比如push一个ViewController之前的数据计算
- Utility 可以执行很长时间,再通知用户结果。比如下载一个文件,给用户下载进度。
- Background 用户不可见,比如在后台存储大量数据
attributes 是并行的还是串行的枚举值(concurrent:并行的, initiallyInactive:串行的)
autoreleaseFrequency 队列的释放规则
切换队列
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
queue.async {
print("当前线程\(Thread.current)------->1")
}
queue.async {
print("当前线程\(Thread.current)------->2")
}
//切换队列
queue.setTarget(queue: DispatchQueue.main)
queue.async {
print("当前线程\(Thread.current)------->3")
}
queue.async {
print("当前线程\(Thread.current)------->4")
}
- 执行结果
- 当前线程<NSThread: 0x7bc2d970>{number = 2, name = (null)}------->2
- 当前线程<NSThread: 0x7b63fe10>{number = 3, name = (null)}------->1
- 当前线程<NSThread: 0x7b6215b0>{number = 1, name = main}------->3
- 当前线程<NSThread: 0x7b6215b0>{number = 1, name = main}------->4
- 延时任务
DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.seconds(3)) { //不是三秒后执行该任务,而是三秒后添加到改队列
//执行的代码
}
队列组
对于串行队列来说最后一个加入的,肯定是最后一个结束。但是并行队列来说我们怎么知道,这些任务都完成了呐。
看看串行队列的处理方式
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.initiallyInactive, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
queue.async {
print("当前线程\(Thread.current)------->1")
}
queue.async {
print("当前线程\(Thread.current)------->2")
}
queue.async {
print("当前线程\(Thread.current)------->3")
}
queue.async {
print("当前线程\(Thread.current)------->4")
DispatchQueue.main.async {
print("执行完了")
}
}
- 打印结果
- 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->1
- 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->2
- 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->3
- 当前线程<NSThread: 0x7a66cea0>{number = 2, name = (null)}------->4
- 执行完了
- 并行队列我们可以使用group去解决这个问题
let group = DispatchGroup()
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->1")
})
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->2")
})
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->3")
})
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->4")
})
let workItem = DispatchWorkItem {
print("执行完成")
} group.notify(queue: queue, work: workItem)
- 打印结果
- 当前线程<NSThread: 0x78e49ae0>{number = 2, name = (null)}------->2
- 当前线程<NSThread: 0x790a4710>{number = 3, name = (null)}------->1
- 当前线程<NSThread: 0x78e53d00>{number = 5, name = (null)}------->3
- 当前线程<NSThread: 0x78e43b60>{number = 4, name = (null)}------->4
- 执行完成
- group中wait方法
和notify不同的是wait是等待一段时间,不管队列的任务是否都完成了,都会调用。
- DispatchWorkItem
let workItem = DispatchWorkItem {
print("开始干活\(Thread.current)")
}
// workItem.perform() //立即执行, 放到了当前main队列中
// DispatchQueue.global().async(execute: workItem) //放到global队列中
- group的enter 和 leave
let group = DispatchGroup()
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->1")
queue.async(execute: {
print("当前线程\(Thread.current)------->2")
})
queue.async(execute: {
print("当前线程\(Thread.current)------->3")
})
})
let workItem = DispatchWorkItem {
print("done")
}
group.notify(queue: queue, work: workItem)
- 打印结果
- 当前线程<NSThread: 0x7b967590>{number = 2, name = (null)}------->1
- done
- 当前线程<NSThread: 0x797807d0>{number = 1, name = main}------->2
- 当前线程<NSThread: 0x7b8bd8b0>{number = 3, name = (null)}------->3
- 我们试想当我们的并行队列里还有其他的并行队列,我们想等他们都操作完之后,在执行下一步,该怎么办。
let group = DispatchGroup()
let queue = DispatchQueue(label: "myQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
queue.async(group: group, execute: {
print("当前线程\(Thread.current)------->1")
group.enter()
queue.async(execute: {
group.leave()
print("当前线程\(Thread.current)------->2")
})
group.enter()
queue.async(execute: {
print("当前线程\(Thread.current)------->3")
group.leave()
})
})
let workItem = DispatchWorkItem {
print("done")
}
group.notify(queue: queue, work: workItem)
- 当前线程<NSThread: 0x7a95e9e0>{number = 2, name = (null)}------->1
- 当前线程<NSThread: 0x7a95e9e0>{number = 2, name = (null)}------->2
- 当前线程<NSThread: 0x7a963a70>{number = 3, name = (null)}------->3
- done
- 并行队列处理数据的时候出现的问题
var array: [Int] = []
for i in 0...100 {
DispatchQueue.global().async(execute: {
array.append(i)
})
}
- 多个线程去更新array时就会出现内存错误。同一个线程访问了同一块地址内存,同时往里面进行写数据。
Demo(61868,0x38bc1c0) malloc: *** error for object 0x7ac34424: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
(lldb)
- 使用信号量解决
var array: [Int] = []
let semaphore = DispatchSemaphore(value: 1)
for i in 0...100 {
DispatchQueue.global().async(execute: {
/*
wait 等待信号量semaphore >= 1时执行
wait返回时 semaphore 减1
*/
_ = semaphore.wait(wallTimeout: .distantFuture)
array.append(i) //只会同时被一个线程访问
/*
signal semaphore 加1
*/
semaphore.signal()
})
}
DispatchQueue.main.async {
print(array.count)
}
- 使用互斥锁
let lock = NSLock()
var array: [Int] = []
for i in 0...100 {
DispatchQueue.global().async(execute: {
lock.lock()
array.append(i)
lock.unlock()
})
}
DispatchQueue.main.async {
print(array.count)
}
死锁(线程之间互相等待对方执行完之后才能接着执行)
- main队列里执行下面代码造成死锁。使用sync编程时要注意死锁的发生。
DispatchQueue.main.sync {
print("死锁了")
}
DispatchSource 调度源
它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。我们来看看它都有哪些类型
- DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。
- DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
- DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
- DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
- DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
- DISPATCH_SOURCE_TYPE_READ:读文件事件。
- DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
- DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
- DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
- DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
- DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。
- 看例子每隔一秒执行操作
private var timer: DispatchSourceTimer!
self.timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags.strict, queue: DispatchQueue.global())
self.timer.scheduleRepeating(deadline: .now(), interval: DispatchTimeInterval.seconds(1)) //每隔一秒执行下面的操作
self.timer.setEventHandler {
DispatchQueue.main.async(execute: {
//回到主线程刷新UI
print(Date())
})
}
self.timer.resume()
你可以使用改装一下做个倒计时。当获取激活码时。
假如我们有5个线程去下载,我们想知道这五个线程的下载进度。
private var dispatchDataSource: DispatchSourceUserDataAdd!
var progress: UInt = 0
self.dispatchDataSource = DispatchSource.makeUserDataAddSource(queue: DispatchQueue.main)
self.dispatchDataSource.setEventHandler {
let data = UInt(self.dispatchDataSource.data) //获取数据中心的data
progress += data
print("上传任务进度:\(progress)")
}
self.dispatchDataSource.resume()
for _ in 0...5 {
DispatchQueue.global().async(execute: {
print("异步处理任务")
self.dispatchDataSource.add(data: 1) //将数组添加数据中心
})
}
- 打印结果
异步处理任务
异步处理任务
异步处理任务
异步处理任务
异步处理任务
异步处理任务
上传任务进度:6