Swift 5.1 - GCD使用总结

作者: Yu_Chou | 来源:发表于2019-12-10 17:59 被阅读0次

    在swift中GCD采用链式调用,较OC而言使用方式更为简单,可读性更高。全文代码均默认在主线程中执行。

    队列的获取与创建

            //串行队列
            let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
            //并发队列
            let concurrent = DispatchQueue(label: "serial",attributes: .concurrent)
            //主队列
            let mainQueue = DispatchQueue.main
            //全局队列
            let global = DispatchQueue.global()
    

    GCD队列都遵循先进先出(FIFO)。所以往并发队列中添加同步任务,其执行顺序和任务的添加顺序相同。全局队列在功能上和并发队列是等价的,所以需要并发队列时,首选使用系统的全局队列。

    这里要注意一点并发队列不能称为并行队列。请参考并发和并行的区别

    同步任务与异步任务

    主队列+同步任务——死锁

    同步任务会阻塞线程,在如下代码中需要优先执行print(2),等待其执行后才能继续往下,但是主队列为串行队列,需要等待当前任务执行完成后才能执行后加入队列的print(2)任务,造成相互等待。

            print(1)
            DispatchQueue.main.sync {
                print(2)
            }
            print(3)
    
    主队列+异步任务——依次执行(不开启新线程)

    以下代码中的print(2)任务会添加到主队列的最后。又由于主线程+异步任务不会开启新线程,以下代码输出1 3 2,顺序固定不变。

            print(1)
            DispatchQueue.main.async {
                print(2)
            }
            print(3)
    
    串行队列+同步任务——依次执行

    以下代码不管每个任务休眠时间多长,输出顺序始终为0...10

            //串行队列
            let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
            for i in 0...10 {
                serial.sync {
                    sleep(arc4random()%3)//休眠时间随机
                    print(i)
                }
            }
    
    串行队列+异步任务——开启一个新线程依次执行

    以下代码输出顺序始终为0...10,并且for循环中的Thread.current的输出始终为同一个新线程

            //串行队列
            let serial = DispatchQueue(label: "serial",attributes: .init(rawValue:0))
            print(Thread.current)//主线程
            for i in 0...10 {
                serial.async {
                    sleep(arc4random()%3)//休眠时间随机
                    print(i,Thread.current)//子线程
                }
            }
    
    并发队列+同步任务——依次执行

    以下代码输出顺序始终为0...10,且线程始终为主线程

            for i in 0...10 {
                DispatchQueue.global().sync {
                    sleep(arc4random()%3)//休眠时间随机
                    print(i,Thread.current)
                }
            }
    
    并发队列+异步任务——开启多个线程并发执行

    以下代码输出顺序随机,且线程信息不同。注意:这里可能不会输出11个不同的线程信息,经过代码测试发现当一个线程的任务执行完成后,如果队列中还有任务,此线程会继续被调度执行后续任务。 将任务数增多,结果更明显。

            for i in 0...10 {
                DispatchQueue.global().async {
                    sleep(arc4random()%3)//休眠时间随机
                    print(i,Thread.current)
                }
            }
    
    并发队列——最大并发数

    GCD并不能无限制的创建线程,如下代码其实最多创建64个子线程,意味着最大并发数为64。参考

           for i in 0...1000 {
                DispatchQueue.global().async {
                    print(i,Thread.current)
                    sleep(10000)
                }
            }
    
    GCD 栅栏

    在swift中栅栏不再是一个单独的方法。而是DispatchWorkItemFlags结构体中的一个属性。sync/async方法的其中一个参数类型即为DispatchWorkItemFlags,所以使用代码如下。这样的调用方式可以更好的理解栅栏,其实它就是一个分隔任务,将其添加到需要栅栏的队列中,以分隔添加前后的其他任务。以下代码栅栏前后均为并发执行。如果将添加栅栏修改为sync则会阻塞当前线程。

            for i in 0...10 {
                DispatchQueue.global().async {
                    print(i)
                }
            }
            DispatchQueue.global().async(flags: .barrier) {
                print("this is barrier")
            }
            for i in 11...20 {
                DispatchQueue.global().async {
                    print(i)
                }
            }
    
    GCD group

    队列组一般用来处理任务的依赖,比如需要等待多个网络请求返回后才能继续执行后续任务。

    使用notify添加结束任务

    必须要等待group中的任务执行完成后才能执行,无法定义超时。

        override func viewDidLoad() {
            let group = DispatchGroup()
            for i in 0...10 {
                DispatchQueue.global().async(group: group) {
                    sleep(arc4random()%3)//休眠时间随机
                    print(i)
                }
            }
            //queue参数表示以下任务添加到的队列
            group.notify(queue: DispatchQueue.main) {
                print("group 任务执行结束")
            }
        }
    
    使用wait进行等待——可定义超时
            let group = DispatchGroup()
            for i in 0...10 {
                DispatchQueue.global().async(group: group) {
                    sleep(arc4random()%10)//休眠时间随机
                    print(i)
                }
            }
            switch group.wait(timeout: DispatchTime.now()+5) {
            case .success:
                print("group 任务执行结束")
            case .timedOut:
                print("group 任务执行超时")
            }
    
    enter()leave()

    enter()是标示一个任务加入到队列,leave()标识一个队列中的任务执行完成。
    注意:enter()leave()必须成对调用
    如果enter()后未调用leave()则不会触发notify,wait也只会timeout。
    如果leave()之前没有调用enter()则会引起crash。

    信号量 semaphore

    GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。

            let semaphore = DispatchSemaphore(value: 0)//创建一个信号量,并初始化信号总量
            semaphore.signal()//发送一个信号让信号量加1
            semaphore.wait()//可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
    
    信号量处理线程同步

    将异步执行任务转化为同步执行任务。如必须等待异步的网络请求返回后才能执行后续任务时。

            let semaphore = DispatchSemaphore(value: 0)
            DispatchQueue.global().async {
                sleep(arc4random()%5)//休眠时间随机
                print("completed")
                semaphore.signal()
            }
            switch semaphore.wait(timeout: DispatchTime.now()+10) {//信号量为0,调用wait后阻塞线程
            case .success:
                print("success")
            case .timedOut:
                print("timeout")
            }
            print("over")
    
    信号量控制最大并发数

    在Operation中可以通过maxConcurrentOperationCount轻松实现控制最大并发数,GCD中需要借助信号量实现。以下代码就限制了最多两个任务并发执行。

            let semaphore = DispatchSemaphore(value: 2)
            for i in 0...10 {
                semaphore.wait()//当信号量为0时,阻塞在此
                DispatchQueue.global().async {
                    sleep(3)
                    print(i,Thread.current)
                    semaphore.signal()//信号量加1
                }
                print("=======================")
            }
    
    使用DispatchSemaphore加锁

    非线程安全,即当一个变量可能同时被多个线程修改。以下代码如果不使用信号量输出是随机值。

            let semaphore = DispatchSemaphore(value: 1)
            var i = 0
            for _ in 1...10 {
                DispatchQueue.global().async {
                    semaphore.wait()//当信号量为0时,阻塞在此
                    for _ in 1...10 {
                        i += 1
                    }
                    print(i)
                    semaphore.signal()//信号量加1
                }
            }
    
    延时任务

    使用GCD执行延时任务指定的并不是任务的执行时间,而是加入队列的时间。所以执行时间可能不太精确。但是任务是通过闭包加入,相较performSelectorAfterDelay可读性更好,也更安全。

            DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2) {
                print("延时任务")
            }
    
    dispatch_once

    在swift3以后dispatch_once被废弃。swift中有更好的定义单例和一次性代码的方式。

    DispatchWorkItem与任务取消

    DispatchWorkItem其实就是用来代替OC中的dispatch_block_t。如果任务是通过DispatchWorkItem定义。在执行之前,可以执行取消操作。注意即使任务已经加入队列,只要还未执行就可以进行取消,但是无法判断任务在队列中的状态,所以一般会根据加入队列的时间确定是否可以取消。

            let workItem = DispatchWorkItem {
                print("延时任务")
            }
            DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+2, execute: workItem)
            sleep(1)
            workItem.cancel()
    
    DispatchWorkItem主动执行
            let workItem = DispatchWorkItem {
                print("workItem")
            }
            workItem.perform()
    
    等待DispatchWorkItem执行完成
            let workItem = DispatchWorkItem {
                sleep(3)
                print("workItem")
            }
            DispatchQueue.global().async(execute: workItem)
            switch workItem.wait(timeout: DispatchTime.now()+5) {
            case .success:
                print("success")
            case .timedOut:
                print("timeout")
            }
    
    DispatchWorkItem执行完成通知
            let workItem = DispatchWorkItem {
                sleep(3)
                print("workItem")
            }
            DispatchQueue.global().async(execute: workItem)
            workItem.notify(queue: DispatchQueue.main) {
                print("completed")
            }
    

    参考:

    GCD最大线程数

    iOS 多线程:『GCD』详尽总结

    相关文章

      网友评论

        本文标题:Swift 5.1 - GCD使用总结

        本文链接:https://www.haomeiwen.com/subject/cwiggctx.html