iOS_Swift

iOS复习-GCD

2018-03-19  本文已影响39人  Mr大喵喵

简介

GCD,全名Grand Central Dispatch,是基于C语言的一套多线程开发API,一听名字就是个狠角色,也是目前苹果官方推荐的多线程开发方式。 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。可以说是使用方便,又不失逼格。

GCD 任务和队列

学习 GCD 之前,先来了解 GCD 中两个核心概念:[任务]和[队列]。

【任务】:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:【同步执行(sync)和异步执行(async)】。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

  1. 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
  2. 只能在当前线程中执行任务,不具备开启新线程的能力。
  1. 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
  2. 可以在新的线程中执行任务,具备开启新线程的能力。

【队列】也只有两种:串行队列(Serial Dispatch Queue)、并发队列(Concurrent Dispatch Queue),队列就是在存放任务也就是线程里的操作,创建了一个队列,也就是开启了一个线程的容器,不是在DispatchQueue.main队列中的线程,都是子线程。

使用GCD就只有两步:创建任务,把任务放进Queue里。
GCD.png

基本使用

  1. 全局异步队列

DispatchQueue.global().async {
//耗时操作
}

  1. 主队列

DispatchQueue.main.async {
//刷新UI
}

3.默认队列(是串行队列)

let queue = DispatchQueue(label: "com.Leo.demoQueue")

  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)
  1. 延迟操作

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
// 2秒后执行
}

  1. 队列组
    队列组就是把任务放在DispatchGroup中(入组),当任务执行完毕时(出组),即当DispatchGroup中没有任务时,调用监听方法notify,注意:入组和出组一定要成对出现,有几个入组,就一定需要有几个出组。只有当每个group.enter()与group.leave()都配对完成后,才会进入到group.notify()执行后续。
注意: group不是对加入到其中的线程进行顺序执行,而是对所加入的这些线程全部都执行完毕后,在group.notify的通知中进行数据的排序等相关操作。
let group = DispatchGroup()  
// A任务入组
group.enter()
// A任务异步操作
DispatchQueue.global().async(group: group, execute: DispatchWorkItem(block: {
        sleep(1)
        print("task A ...")
        // 出组
        group.leave()
}))
// B任务入组
group.enter()
// B任务异步操作
DispatchQueue.global().async(group: group, execute: DispatchWorkItem(block: {
        sleep(2)
        print(" task B ...")
        // 出组
        group.leave()
}))
// 主线程监听,只有当队列组中没有任务,才会执行闭包。如果多次调用该方法,每次都会去检查队列组中是否有任务,如果没有任务才执行
group.notify(queue: DispatchQueue.main) { 
        print("complete...")
}

死锁

打印结果: 21521449306_.pic.jpg

还有一种死锁,简单的代码如下

 queueA.sync {
    queueB.sync {
        queueC.sync {
            queueA.sync {

            }
        }
    }
}

死锁的原因很简单,形成了一个相互阻塞的环。

知识点

  1. 对于单核CPU来说,不存在真正意义上的并行,所以,多线程执行任务,其实也只是一个人在干活,CPU的调度决定了非等待任务的执行速率,同时对于非等待任务,多线程并没有真正意义提高效率。

  2. 线程可以简单的认为就是一段代码+运行时数据。

  3. 同步执行会在当前线程执行任务,不具备开辟线程的能力或者说没有必要开辟新的线程。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会返回,从而阻塞同一串行队列中外部方法的执行。

  4. 异步执行dispatch函数会直接返回,Block函数我们可以认为它会在下一帧加入队列,并根据所在队列目前的任务情况无限下一帧执行,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开辟新线程的必要,但是异步执行不一定会开辟新线程。

  5. 只要是队列,肯定是FIFO(先进先出),但是谁先执行完要看第1条。

  6. 只要是串行队列,肯定要等上一个任务执行完成,才能开始下一个任务。但是并行队列当上一个任务开始执行后,下一个任务就可以开始执行。

  7. 想要开辟新线程必须让任务在异步执行,想要开辟多个线程,只有让任务在并行队列中异步执行才可以。执行方式和队列类型多层组合在一定程度上能够实现对于代码执行顺序的调度。

  8. 同步+串行:未开辟新线程,串行执行任务;同步+并行:未开辟新线程,串行执行任务;异步+串行:新开辟一条线程,串行执行任务;异步+并行:开辟多条新线程,并行执行任务;在主线程中同步使用主队列执行任务,会造成死锁。

  9. 对于多核CPU来说,线程数量也不能无限开辟,线程的开辟同样会消耗资源,过多线程同时处理任务并不是你想像中的人多力量大。

QoS

QoS的全称是quality of service。刚刚上文也使用到,在Swift 3中,它是一个结构体,用来制定队列或者任务的重要性

何为重要性呢?就是当资源有限的时候,优先执行哪些任务。这些优先级包括CPU时间,数据IO等等,也包括ipad muiti tasking(两个App同时在前台运行)。

从上到下优先级依次降低。
依次含义如下:

在GCD中,指定QoS有以下两种方式

方式一,创建一个指定QoS的queue

let backgroundQueue = DispatchQueue(label: "com.leo.backgroundQueu", qos: .background)
backgroundQueue.async {
//在QoS为background下运行
}

方式二,在提交block的时候,指定QoS

queue.async(qos: .background) {
//在QoS为background下运行
}

after(延迟执行)

GCD可以通过asyncAfter和syncAfter来提交一个延迟执行的任务
比如

let deadline = DispatchTime.now() + 2.0
NSLog("Start")
DispatchQueue.global().asyncAfter(deadline: deadline) {
NSLog("End")
}

可以看到,两秒后打印了End

2017-01-05 22:42:04.781 GCD[1617:36711] Start
2017-01-05 22:42:06.972 GCD[1617:36768] End

延迟执行还支持一种模式DispatchWallTime

let walltime = DispatchWallTime.now() + 2.0
NSLog("Start")
DispatchQueue.global().asyncAfter(wallDeadline: walltime) {
NSLog("End")
}

这里的区别就是

Synchronization

通常,在多线程同时会对一个变量(比如NSMutableArray)进行读写的时候,我们需要考虑到线程的同步。举个例子:比如线程一在对NSMutableArray进行addObject的时候,线程二如果也想addObject,那么它必须等到线程一执行完毕后才可以执行。

lock.lock()
//Do something
lock.unlock()

使用锁有一个不好的地方就是:lock和unlock要配对使用,不然极容易锁住线程,没有释放掉。
使用GCD,队列同步有另外一种方式 - sync,讲属性的访问同步到一个queue上去,就能保证在多线程同时访问的时候,线程安全。

class MyData{
    private var privateData:Int = 0
    private let dataQueue = DispatchQueue(label: "com.leo.dataQueue")
    var data:Int{
        get{
            return dataQueue.sync{ privateData }
        }
        set{
            dataQueue.sync { privateData = newValue}
        }
    }
}
Barrier# (栅栏函数)
  • barrier翻译过来就是屏障。在一个并行queue里,很多时候,我们提交一个新的任务需要这样做。
  • queue里之前的任务执行完了新任务才开始
    新任务开始后提交的任务都要等待新任务执行完毕才能继续执行

1.实现高效率的数据库访问和文件访问
2.避免数据竞争

典型的场景就是往NSMutableArray里addObject。
例如:

let concurrentQueue = DispatchQueue(label: "com.leo.concurrent", attributes: .concurrent)
concurrentQueue.async {
    readDataTask(label: "1")
}

concurrentQueue.async {
    readDataTask(label: "2")
}
concurrentQueue.async(flags: .barrier, execute: {
    print("Task from barrier 1 begin")
    sleep(3)
    print("Task from barrier 1 end")
})

concurrentQueue.async {
    readDataTask(label: "3")
}
concurrentQueue.async {
    readDataTask(label: "4")
}

然后看到输出

17:14:19.690 Dispatch[15609:245546] Start data task1
17:14:19.690 Dispatch[15609:245542] Start data task2
17:14:22.763 Dispatch[15609:245546] End data task1
17:14:22.763 Dispatch[15609:245542] End data task2
17:14:22.764 Dispatch[15609:245546] Task from barrier 1 begin
17:14:25.839 Dispatch[15609:245546] Task from barrier 1 end
17:14:25.839 Dispatch[15609:245546] Start data task3
17:14:28.913 Dispatch[15609:245546] End data task3

执行的效果就是:barrier任务提交后,等待前面所有的任务都完成了才执行自身。barrier任务执行完了后,再执行后续执行的任务。

上一篇下一篇

猜你喜欢

热点阅读