iOS 常用有三种线程管理方式,分别是 NSThread、GCD 与 NSOperation,现在我们先来了解一下其中的 GCD
串行与并行针对的是任务队列,而同步与异步,针对的则是线程。
Serial Queue + Sync 序列执行+同步
Serial Queue + Async 序列执行 + 异步 (按顺序)
Concurrent Queue + Sync 并发 + 同步 (按顺序)
Concurrent Queue + Async 并发 + 异步 (真正多线程)(不按顺序)
自定义串行队列有能力启动主线程和后台线程(只能启动一个后台线程),不会发生死锁。同步任务,会自动安排在主线程执行;遇到异步任务,自动安排在后台线程执行,所以不会死锁。
//1. 只带尾随闭包
let item1 = DispatchWorkItem {
print("item1")
}
//2. 指定qos(执行优先级)或flags(特殊行为标记)
let item2 = DispatchWorkItem(qos: .userInteractive, flags: .barrier) {
print("item2")
}
Main queue (串行队列) 仅能运行在主线程上
let mainQueue = DispatchQueue.main
Global queue(并行队列 Concurrent)
let globalQueue = DispatchQueue.global()
Custom queue(默认串行)
//串行队列,label名字随便取
let serialQueue = DispatchQueue(label: "test")
//并行队列
let concurrentQueue = DispatchQueue(label: "test", attributes: .concurrent)
添加任务
异步
let mainQueue = DispatchQueue.main
mainQueue.async(execute: item1)
let globalQueue = DispatchQueue.global()
globalQueue.async(execute: item1)
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async(execute: item1)
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
concurrentQueue.async(execute: item1)
同步
let mainQueue = DispatchQueue.main
mainQueue.sync(execute: item1) // 必定引起死锁
let globalQueue = DispatchQueue.global()
globalQueue.sync(execute: item1)
let serialQueue = DispatchQueue(label: "serial")
serialQueue.sync(execute: item1)
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
concurrentQueue.sync(execute: item1)
队列死锁,而不是线程死锁。主队列添加同步任务造成死锁的根本原因:
可把多个任务放到 group,方便管理。
当组内所有任务执行完成,GCD API 发送相应的通知。
自定义串行队列一个异步或同步任务(A)嵌套另一个同步任务(B)会引起死锁。
A、B任务等效为:A1 -> B -> A2。
// 当前任务
let queue = DispatchQueue.init(label: "name")
queue.sync {
// 死锁。同步要等外层执行后这里才能执行,而外层的执行需要这里先执行完。
queue.sync {
print(Thread.current) // 同步任务
}
// 当前任务
print(Thread.current)
}
并行队列添加同步任务不会死锁,因为同步任务被安排在主线程执行,异步任务被安排在后台线程执行。
所有的同步任务最终都要安排到主线程运行,主线程运行长耗时任务会导致界面严重卡顿。
// 这两种方式都会使界面卡顿(15s)
override func viewDidAppear(_ animated: Bool) {
//1. 全局队列执行同步任务
DispatchQueue.global().sync {
sleep(15)//当前线程休眠15秒
}
//2. 主队列执行异步任务
DispatchQueue.main.async {
sleep(15)//当前线程休眠15秒
}
}
GCD 正确做法:A、B都定义成异步任务,在并行队列中嵌套异步任务,最后切换到主队列去刷新UI
let queue = DispatchQueue(label: "com.apple.request", attributes: .concurrent)
//异步执行
queue.async {
print("开始请求数据 \(Date()) thread: \(Thread.current)")
sleep(10)//模拟网络请求
print("数据请求完成 \(Date()) thread: \(Thread.current)")
//异步执行
queue.async {
print("开始处理数据 \(Date()) thread: \(Thread.current)")
sleep(5)//模拟数据处理
print("数据处理完成 \(Date()) thread: \(Thread.current)")
//切换到主队列,刷新UI
DispatchQueue.main.async {
print("UI刷新成功 \(Date()) thread: \(Thread.current)")
}
}
}
DispatchQueue.main 自动生成的主队列对象,可获取
DispatchQueue.global
DispatchQueue() 默认是序列队列
DispatchQueue(.concurrent) 并发队列
同步任务都会被分配到主线程。
global、自定义 serial 队列、并发队列,都有能力把异步任务分配到子线程。serial 只能开启一个子线程(做并发任务足够了)。
同队列中,同步任务会等待前方任务执行完再执行。(先来先服务)
number 是队列标识,name 是线程标识,使用 serial 安排同步任务和异步任务,会将同步的分给主线程,把异步的分给后台某个匿名线程。
let queue = DispatchQueue.init(label: "hei")
queue.async {
print(Thread.current)
}
queue.sync {
queue.async {
print(Thread.current)
} // 虽然比下面的 print 早,但是把这个异步任务发到另外线程这个过程需要时间。
print(Thread.current)
}
queue.async {
print(Thread.current)
}
queue.sync {
print(Thread.current)
}
// 结果
// {number = 5, name = (null)}
// <_NSMainThread: 0x7fde7cb06570>{number = 1, name = main}
// {number = 5, name = (null)}
// {number = 5, name = (null)}
// <_NSMainThread: 0x7fde7cb06570>{number = 1, name = main}
如果 serial 队列嵌套同步任务将会死锁。
// 当前任务
let queue = DispatchQueue.init(label: "name")
queue.sync {
// 死锁。同步要等外层执行后这里才能执行,而外层的执行需要这里先执行完。
queue.sync {
print(Thread.current) // 同步任务
}
// 当前任务
print(Thread.current)
}