一、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 是一个结构体类型,该结构体提供了两个静态变量:concurrent
和initiallyInactive
(注意,没有代表串行队列的静态变量)。如果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有success
、timeOut
两个枚举值,分别表示在等待时长内,任务执行完成和未完成。我们还可以通过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
/*
其他并发操作
*/
}
}