GCD
开发中多线程技术使用频率相当之高,近日得空系统梳理一下GCD的使用,写此笔记一篇。便于以后查阅!
此文不添加效果图,如有需求根据笔记自写demo
1、获取主队列
let mainQueue = DispatchQueue.main
mainQueue.async {
print("主线程----> %@ ", Thread.current)
}
2、创建默认配置队列
let globalQueue = DispatchQueue.global()
globalQueue.async {
print("默认配置的全局队列----> %@ ", Thread.current)
}
3、指定队列属性
//(3)配置队列属性
// label: 队列标签
// qos: 队列优先级
// attributes: 队列形式.默认串行,设置为.concurrent代表是并行队列
// autoreleaseFrequency: 自动释放频率
// target: 很少用,文章最后补充说明该参数
let label = "com.kpp.queue_1"
let qos = DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: nil)
queue.async {
print("do something with the thread ---> %@", Thread.current)
}
- laebl: 队列标签
qos: 队列优先级
- case background //后台
- case utility //公共的
- case
default
//默认的 - case userInitiated //用户期望优先级(不要放太耗时的操作)
- case userInteractive //用户交互(跟主线程一样)
- case unspecified //不指定
attributes: 队列形式.默认串行,设置为.concurrent代表是并行队列
- autoreleaseFrequency: 自动释放频率。有些队列是会在执行完任务后自动释放的,有些比如Timer等是不会自动释放的,是需要手动释放。
- case never
- case inherit
- case workItem
target: 暂时未知
qos和原有的对应关系为:
DISPATCH_QUEUE_PRIORITY_HIGH: .userInitiated
DISPATCH_QUEUE_PRIORITY_DEFAULT: .default
DISPATCH_QUEUE_PRIORITY_LOW: .utility
DISPATCH_QUEUE_PRIORITY_BACKGROUND: .background
4、延时
/// 延时执行
func delayExecute() {
// 延时时间设置
//let delay = DispatchTime.now() + DispatchTimeInterval.seconds(5)
//(1)主队列延时执行
print(Date())
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
print(Date())
}
//(2)子线程延时执行
let label = "com.kpp.delayQueue"
print(Date())
let delayQueue = DispatchQueue(label: label)
delayQueue.asyncAfter(deadline: DispatchTime.now() + 2) {
print(Date())
}
}
5、异步执行
需要注意的是DispatchQueue
的默认初始化方法创建的就是一个同步队列,如果要创建并发的队列,在attributes
中声明concurrent
主线程内,即使调用async
异步函数同样为同步执行
(1)自定义线程异步执行
// 异步执行,将attributes设置为 .concurrent,调用异步函数 async
let label = "com.kpp.queue_1"
let qos = DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label,
qos: qos,
attributes: attributes,
autoreleaseFrequency: autoreleaseFrequency,
target: nil)
queue.async {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue.async {
for _ in 0..<10 {
print("***********************")
}
}
(2)获取默认全局队列异步执行
//(2)全局队列异步执行
DispatchQueue.global().async {
for _ in 0..<1000 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
DispatchQueue.global().async {
for _ in 0..<1000 {
print("***********************")
}
}
6、同步执行
需要注意的是DispatchQueue
的默认初始化方法创建的就是一个同步队列,如果要创建并发的队列,在attributes
中声明concurrent
同步执行可以有很多种方式实现:
(1)创建默认线程
// 需要注意的是DispatchQueue的默认初始化方法创建的就是一个同步队列,如果要创建并发的队列,在attributes中声明concurrent。
//(1)默认方法创建的是同步队列,即使用异步函数 async
let queue_1 = DispatchQueue(label: "com.kpp.queue_1")
// 使用异步async或者sync函数,均为同步顺序执行
queue_1.async {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue_1.async {
for _ in 0..<10 {
print("***********************")
}
}
(2)主线程同步执行
//(2)即使调用主线程,且使用异步async或者sync函数,均为同步顺序执行
DispatchQueue.main.async {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
DispatchQueue.main.async {
for _ in 0..<10 {
print("***********************")
}
}
(3)自定义线程同步执行
//(3)自定义参数的线程,可以根据调用不同的函数(async/sync)执行异步或同步操作
let queue = DispatchQueue(label: "com.kpp.queue_1",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
queue.sync {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue.sync {
for _ in 0..<10 {
print("***********************")
}
}
(4)全局队列同步执行
//(4)获取全局队列同步执行
DispatchQueue.global().sync {
print(Thread.current)
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
DispatchQueue.global().sync {
print(Thread.current)
for _ in 0..<10 {
print("***********************")
}
}
7、异步执行多个任务完成后,再执行下一个任务
/// 队列Group,任务依赖
func groupNotify() {
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.kpp.queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("***********************")
}
}
// queue线程的任务完成后,通知该方法
group.notify(flags: [], queue: queue) {
print("任务执行完了!!!")
}
}
8、线程安全 .barrier
在以上的async
/sync
方法中有个参数我们一直没有解释,那就是flags
。该参数我们可以设置为[]
或者省略掉,或者写成线程安全的 .barrier
。
9、DispatchWorkItem
DispatchQueue
执行操作的闭包函数,除了直接写操作代码之外,还可以传入一个DispatchWorkItem
,并且可以在DispatchWorkItem.wait()
之后调用线程执行后的操作。
两个初始化方法:
前者,有默认参数
后者可以传入优先级参数qos,和DispatchWorkItemFlags
DispatchWorkItem(block: <#T##() -> Void#>)
DispatchWorkItem(qos: <#T##DispatchQoS#>, flags: <#T##DispatchWorkItemFlags#>, block: <#T##() -> Void#>)
func dispatchWorkItem() {
let queue = DispatchQueue(label: "com.kpp.queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
// 默认参数初始化
let workItem_1 = DispatchWorkItem {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
// 自定义参数初始化,.barrier在workItem_1完成后,同步执行workItem_2
let workItem_2 = DispatchWorkItem(qos: .default, flags: DispatchWorkItemFlags.barrier, block: {
for _ in 0..<10 {
print("***********************")
}
})
// 同步sync或者异步async
queue.async(execute: workItem_1)
queue.async(execute: workItem_2)
// workItem_1完成后执行打印
workItem_1.wait()
print("after workItem_1.wait")
// workItem_2完成后执行打印
workItem_2.wait()
print("after workItem_2.wait")
}
10、Group队列,任务依赖
关于该技术的应用场景,看到“依赖”两个字应该也能理解的差不多了!
以下代码是简单的一个依赖关系,更复杂的线程依赖,可以根据该知识点合理使用达到目的。
/// 队列Group,任务依赖
func groupNotify() {
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.kpp.queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("***********************")
}
}
// queue线程的任务完成后,通知该方法
group.notify(queue: queue) {
print("任务执行完了!!!")
}
}
11、Group任务等待
/// 任务等待
func groupWait() {
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.kpp.queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺☺")
}
}
queue.async(group: group, qos: .default, flags: []) {
for _ in 0..<10 {
print("***********************")
}
}
// 等待上面任务执行,会阻塞当前线程。
// 超时就执行下面的.timedOut,上面的依然继续执行。
// 未超时完成,则不再继续等待,直接执行 .success
// 也可以无限等待 .distantFuture
let result = group.wait(timeout: .now() + 2)
switch result {
case .success:
print("上面的任务已完成且未超时")
case .timedOut:
print("执行上面的任务已超时,与现在的任务异步执行")
}
print("以上所有任务完成后才会执行到这里!!!")
}
12、group.enter()和group.leave()
当线程执行的操作不能够与group绑定时,我们又需要在任务完成后得到通知。此时,可以使用 group.enter()和group.leave(),且要成对使用。这种情况多用在同时多个网络请求时,多个网络请求均结束后通知我们做什么事儿。
(1)模拟网络请求的函数:
// 声明一个通用的闭包
typealias CompletionHandle = (_ result: String?, _ error: Error?)->()
/// 模拟网络请求
func netAction(param: String, completion: @escaping CompletionHandle) {
// 全局队列,模拟异步并行网络请求
DispatchQueue.global().async {
// 模拟网络请求的一个耗时过程
for _ in 0...100000 {
print(param)
}
completion("success", nil)
}
}
(2)网络请求加入指定队列和线程
此处将一个网络请求加入自己创建的队列和线程。completion
闭包回调一定要放入自己创建的线程中执行,并将请求全过程放入group.enter()
和group.leave()
监听。
// 网络请求任务加入队列执行
func netAction_group(param: String, runGroup: DispatchGroup, runQueue: DispatchQueue, completion: @escaping CompletionHandle) {
runGroup.enter()
netAction(param: param) { (message, error) in
runQueue.async(execute: {
completion(message, error)
runGroup.leave()
})
}
}
(3)调用测试
/// 监听多个网络请求结束
func netGroupNotify() {
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.kpp.netQueue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .never,
target: nil)
self.netAction_group(param: "111", runGroup: group, runQueue: queue) { (message, error) in
print("111" + message!)
}
self.netAction_group(param: "222", runGroup: group, runQueue: queue) { (message, error) in
print("222" + message!)
}
group.notify(queue: queue) {
print("网络请求执行完毕!!!!")
}
}
13、信号量
该知识点参考自这篇文章
(1)概念解释
信号量是一个整数,在创建的时候会有一个初始值,这个初始值往往代表我要控制的同时操作的并发数。在操作中,对信号量会有两种操作:信号通知与等待。信号通知时,信号量会+1,等待时,如果信号量大于0,则会将信号量-1,否则,会等待直到信号量大于0。什么时候会大于零呢?往往是在之前某个操作结束后,我们发出信号通知,让信号量+1。
说完概念,我们来看看GCD中的三个信号量操作:
dispatch_semaphore_create:创建一个信号量(semaphore)
dispatch_semaphore_signal:信号通知,即让信号量+1
dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1
在使用的时候,往往会创建一个信号量,然后进行多个操作,每次操作都等待信号量大于0再操作,同时信号昂-1,操作完后将信号量+1,类似下面这个过程:
dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次循环操作) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 操作
dispatch_semaphore_signal(sema);
});
}
上面代码表示我要操作100次,但是控制允许同时并发的操作最多只有5次,当并发量达到5后,信号量就减小到0了,这时候wait操作会起作用,DISPATCH_TIME_FOREVER表示会永远等待,一直等到信号量大于0,也就是有操作完成了,将信号量+1了,这时候才可以结束等待,进行操作,并且将信号量-1,这样新的任务又要等待。
- 这篇文章里关于信号量的一种形象比喻写的很好,拿来贴上辅助理解:
下面就以停车做例子说明信号量这几个函数的使用。
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了dispatch_semaphore_create(long value)
调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;
当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
(2)代码示例
加一段小代码演示一下,加深理解:
// MARK: - 信号量
func semaphore() {
//初始化信号量, 计数为三
let mySemaphore = DispatchSemaphore(value: 3)
for i in 0...10 {
//获取信号量,信号量减1,为0时候就等待,会阻碍当前线程
//mySemaphore.wait()
//阻碍时等两秒信号量还是为0时将不再等待, 继续执行下面的代码
_ = mySemaphore.wait(timeout: DispatchTime.distantFuture)
DispatchQueue.global().async {
for _ in 0...3 {
sleep(1)
print(i)
}
mySemaphore.signal()
print("---------------------")
}
}
}
14、GCD 暂停、继续
(1)暂停
GCD暂停,正在执行的任务不能暂停。但是未执行的任务(比如延迟执行的任务),可以在任务未开始前暂停:
queue.suspend()
(2)继续
正在执行中的任务无法暂停,当然也不会有继续。但是未执行或等待执行的任务,暂停后,可以恢复待执行的状态:
// MARK: - GCD取消任务
func cancleQueue() {
// 延迟五秒钟执行,给任务暂停和继续的操作留下时间
myQueue.asyncAfter(deadline: DispatchTime.now() + 5) {
for i in 0...1000000 {
print(i)
}
}
}
var isSuspend = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isSuspend = !isSuspend
if isSuspend {
print("暂停")
myQueue.suspend()
} else {
myQueue.resume()
print("继续")
}
}