Swift GCD

作者: 西南柯北 | 来源:发表于2018-03-19 14:17 被阅读24次

    一、DispatchQueue

    DispatchQueue 分为串行和并发,它的完整初始化方法为:

    init(label: String, qos: DispatchQoS = default, attributes: DispatchQueue.Attributes = default, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = default, target: DispatchQueue? = default)
    

    可见,这些参数中,除了label,其它都有默认值(label表示该队列的标签,建议传值为反向域名字符串,如:com.onevcat.Kingfisher.Animator.preloadQueue)。

    当除label外的参数都使用默认值时,初始化方法返回的便是串行队列。如果需要返回并发队列,参数attributes传值为.concurrent即可。DispatchQueue.Attributes 是一个结构体类型,该结构体提供了两个静态变量:concurrentinitiallyInactive(注意,没有代表串行队列的静态变量)。如果attributes参数传值为initiallyInactive, 任务不会自动执行,而是需要开发者手动调用activate()触发。但是代码依然是串行进行的,如果想要手动触发、并行执行任务,可以指定attributes参数接受一个数组: [.concurrent, .initiallyInactive]

    参数qos代表队列执行的优先级,有六种优先级可供选择:

    unspecified
    background
    default
    utility
    userInteractive
    userInitiated
    

    优先级从高到低依次为userInteractive>userInitiated>utility>background, 而default与unspecified介于userInteractive与background之间,具体有系统决定。

    DispatchQueue.AutoreleaseFrequency有三种属性值.inherit.workItem.never
    .inherit:不确定,之前默认的行为也是现在的默认值
    .workItem:为每个执行的任务创建自动释放池,项目完成时清理临时对象
    .never:GCD不为您管理自动释放池

    参数target 用于指定即将创建的队列与队列target优先级相同。也可通过setTarget(queue: DispatchQueue?)函数指定与queue相同的优先级。

    除了开发者自己创建队列,还可以通过DispatchQueue.main获取主队列(主队列也属于串行队列)、DispatchQueue.global(qos: DispatchQoS.QoSClass) 获取全局并发队列。

    创建好了队列,通过sync { /*任务*/ }async { /*任务*/ } 将任务追加到队列中。串行队列或并发队列与sync或async组合总结:

    串行队列 + sync : 队列中的任务在当前线程中依次执行,后面追加的任务会等到前面追加的任务执行完了才开始执行,不开新线程。当前线程取任务执行的队列不能与该串行队列相同,否则会发生线程死锁。
    串行队列(非主队列) + async : 队列中的任务在新线程中依次执行。
    主队列 + async : 将任务追加到主队列,当主队列中的其他任务执行完之后才会执行,并且在在主线程中执行。
    并发队列 + sync : 队列中的任务在当前线程中依次执行。
    并发队列 + async : 队列中的任务在新线程中并发执行。

    不管哪种组合,队列中的任务出列的方式都是FIFO。

    有时候希望追加到queue中的任务暂不执行,等待某一时刻执行,这时候可使用队列的suspend()函数和resume()函数。suspend()函数使队列的暂停计数加1,resume()函数使队列的暂停计数减一。

    需要注意:
    1、suspend()和resume()需要成对出现,否则会crash。
    2、suspend()和resume()函数只对自己创建的队列有效,对系统提供的全局队列无效。
    3、suspend()和 resume()对队列中的还未执行的任务有效,对于正在执行的任务无效。

    二、DispatchGroup

    在追加到DispatchQueue中的多个处理全部结束后想执行结束处理,这个时候就可用到DispatchGroup。示例如下:

    let group = DispatchGroup()
    let queue = DispatchQueue.global()
    queue.async(group: group) {
         print("任务一")
    }
    queue.async(group: group) {
          print("任务二")
    }
    queue.async(group: group) {
         print("任务三")
    }
    group.notify(queue: DispatchQueue.main) {
         print("完成任务一、二、三")
    }
    queue.async {
         print("任务四")
    }
    

    运行结果:


    其中,queue既可以是同一个队列,也可以是不同的队列,既可以是串行队列,也可以是并发队列。
    另外,也可以使用group的 group.wait(timeout: DispatchTime)group.wait(wallTimeout: DispatchWallTime)函数。wait 函数的参数表示等待的时间,默认是 DispatchTime.distantFuture,表示永久等待。wait函数会阻塞当前线程,即当执行的时间到了等待的时长,才会执行后面的代码。wait函数返回值是枚举类型DispatchTimeoutResult,DispatchTimeoutResult有successtimeOut两个枚举值,分别表示在等待时长内,任务执行完成和未完成。
    我们还可以通过group的enter()函数和leave()函数显式表明任务是否执行完成。代码如下:
    let group = DispatchGroup()
    let queue = DispatchQueue.global()
    group.enter()
    queue.async {
         print("任务一")
         group.leave()
    }
    group.enter()
    queue.async {
         print("任务二")
         group.leave()
    }
    group.enter()
    queue.async {
        print("任务三")
        group.leave()
    }
    group.notify(queue: DispatchQueue.main) {
        print("完成任务一、二、三")
    }
    queue.async {
        print("任务四")
    }
    

    运行结果:


    enter()leave()必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。

    三、asyncAfter

    该函数用于延时操作。代码如下:

    DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now()+3) {
        print("执行任务")
    } 
    

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {
        print("执行任务")
    }
    

    注意, asyncAfter函数并不是在指定时间后执行处理,而是在指定时间后将任务追加到队列中。

    asyncAfter函数的第一个参数可以是DispatchTime类型的值,也可以是DispatchWallTime类型的值。

    DispatchTime 表示相对时间(相对设备启动的时间,当设备休眠时,计时也会暂停),精度为纳秒级。DispatchTime.now() 获取当前相对时间,DispatchTime.now()基于当前时间三秒后的时间,表达式中的3也可以使用DispatchTimeInterval.seconds(3)替换,或者用其他的时间单位:毫秒级DispatchTimeInterval.milliseconds(Int) 、微秒级DispatchTimeInterval.milliseconds(Int)、纳秒级DispatchTimeInterval.nanoseconds(Int)
    DispatchWallTime 表示绝对时间(系统时间,设备休眠计时不暂停),精度是微秒。DispatchWallTime的用法和DispatchTime差不多。

    四、DispatchWorkItem

    DispatchWorkItem可以将任务封装成DispatchWorkItem对象。

    let workItem = DispatchWorkItem.init {
          print("执行任务")
    }
    

    可以调用workItem的perform()函数执行任务,也可以将workItem追加到DispatchQueue或DispatchGroup中。以上所有传block的地方都可换成DispatchWorkItem对象。
    DispatchQueue还可以使用notify函数观察workItem中的任务执行结束,以及通过cancel()函数取消任务。

    另外,workItem也可以像DispatchGroup一样调用wait()函数等待任务完成。需要注意的是,追加workItem的队列或调用perform()所在的队列不能与调用workItem.wait()的队列是同一个队列,否则会出现线程死锁。

    DispatchWorkItem的完整初始化方法:

    init(qos: DispatchQoS, flags: DispatchWorkItemFlags, block: () -> Void)
    

    DispatchQoS前面已经说过,不再赘述。DispatchWorkItemFlags类型的变量有六种:

    static let assignCurrentContext: DispatchWorkItemFlags
    static let barrier: DispatchWorkItemFlags
    static let detached: DispatchWorkItemFlags
    static let enforceQoS: DispatchWorkItemFlags
    static let inheritQoS: DispatchWorkItemFlags
    static let noQoS: DispatchWorkItemFlags
    

    为了高效地读写数据库或文件,通常需要将读写处理追加到并发队列
    中异步执行,为了使读写操作不会引发数据竞争的问题,写入操作不能与其他的写入操作以及包含读取任务的操作并发处理,这时便可设置flag的值为.barrier。代码如下:

    let queue = DispatchQueue.init(label: "com.codansYC.queue", attributes: DispatchQueue.Attributes.concurrent)
    queue.async {
         print("读数据1")
    }
    queue.async {
         print("读数据2")
    }
    let workItem = DispatchWorkItem.init(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier) {
         print("开始写数据------写数据完成")
    }
    queue.async(execute: workItem)
    queue.async {
         print("读数据3")
    }
    queue.async {
         print("读数据4")
    }
    

    运行结果:


    注意,barrier只对自己创建的并发队列才有效,对系统提供的全局并发队列无效。

    五、DispatchQueue.concurrentPerform

    sync函数和Dispatch Group的关联API。
    DispatchQueue.concurrentPerform 会按指定次数异步执行任务,并且会等待指定次数的任务全部执行完成,即会阻塞线程。建议在子线程中使用。

    DispatchQueue.global().async {
         DispatchQueue.concurrentPerform(iterations: 5) { (i) in
             print("执行任务\(i+1)")
         }
         print("任务执行完成")
    }
    
    运行结果:

    六、DispatchSemaphore

    信号量。用于控制访问资源的数量。比如系统有两个资源可以被利用,同时有三个线程要访问,只能允许两个线程访问,第三个会等待资源被释放后再访问。
    信号量的初始化方法:DispatchSemaphore.init(value: Int),value表示允许访问资源的线程数量,当value为0时对访问资源的线程没有限制。
    信号量配套使用wait()函数与signal()函数控制访问资源。
    wait函数会阻塞当前线程直到信号量计数大于或等于1,当信号量大于或等于1时,将信号量计数-1, 然后执行后面的代码。signal()函数会将信号量计数+1。

    信号量是GCD同步的一种方式。前面介绍过的DispatchWorkItemFlags.barrier是对queue中的任务进行批量同步处理,sync函数是对queue中的任务单个同步处理,而DispatchSemaphore是对queue中的某个任务中的某部分(某段代码)同步处理。此时将DispatchSemaphore.init(value: Int)中的参数value传入1。代码如下:

    var arr = [Int]()
    let semaphore = DispatchSemaphore.init(value: 1) // 创建信号量,控制同时访问资源的线程数为1
    for i in 0...100 {
        DispatchQueue.global().async {
                    
            /*
            其他并发操作
            */
                    
            semaphore.wait() // 如果信号量计数>=1,将信号量计数减1;如果信号量计数<1,阻塞线程直到信号量计数>=1
            arr.append(i)
            semaphore.signal() // 信号量计加1
                    
            /*
            其他并发操作
            */
         }
    }
    

    相关文章

      网友评论

        本文标题:Swift GCD

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