GCD笔记

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("继续")
    }
}

猜你喜欢

转载自blog.csdn.net/kangpengpeng1/article/details/79897416
gcd
今日推荐