Swift GCD

一、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
                
        /*
        其他并发操作
        */
     }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容