Swift GCD(Grand Central Dispatch)探索

房泉
2023-12-01

GCD知识点梳理

GCD(Grand Central Dispatch)是苹果推出的用于多核并行编程的技术.关于 GCD 编程一定要知道的知识点:

1、GCD 是什么:GCD 是一种异步执行任务的技术,能够帮助程序员更方便地实现多线程编程,提高程序的性能。

2、GCD 的优势:GCD 可以通过自动化任务的管理来避免开发者手动管理线程和队列,从而减少代码复杂度和错误率。

3、GCD 中的队列:GCD 中有两种队列,串行队列和并发队列。串行队列每次只能执行一个任务,而并发队列可以同时执行多个任务。

4、GCD 中的任务:GCD 中的任务可以是同步任务或异步任务。同步任务会阻塞当前线程,直到该任务完成为止,而异步任务会在后台执行,不会阻塞当前线程。

5、GCD 中的执行方法:GCD 提供了三种任务执行方法,分别是同步执行、异步执行和栅栏执行。同步执行是在当前线程中执行任务,异步执行是在后台线程中执行任务,而栅栏执行可以在任务执行前和执行后插入其他任务。

6、GCD 中的 Dispatch Group:Dispatch Group 可以将一组任务添加到一个组中,然后等待组中的所有任务执行完成后再执行其他操作。

7、GCD 中的 Dispatch Semaphore:Dispatch Semaphore 可以用来管理资源的访问,可以控制同时访问资源的线程数。

8、GCD 中的 Dispatch Source:Dispatch Source 可以用来监控各种系统事件,如文件系统变化、定时器事件等等。

GCD 中的队列

在GCD中,队列是用于管理任务的一种机制。队列可以分为两种类型:

  • 串行队列
  • 并发队列

不同类型的队列在任务执行的方式上有很大不同。

串行队列按照任务添加到队列中的顺序,一个一个地执行,每次只有一个任务在执行。也就是说,任务按照FIFO的顺序执行。在串行队列中,任务必须一个接一个地执行,因此这种队列适用于需要顺序执行任务的场景,比如在某个任务完成后执行另一个任务。

并发队列可以同时执行多个任务,但是任务的执行顺序并不一定按照添加到队列中的顺序,而是取决于系统资源的可用性和任务的优先级。在并发队列中,任务是并发执行的,所以适用于需要同时执行多个任务的场景,比如图片下载或者数据处理。

在GCD中,队列还分为两种

  • 主队列
  • 自定义队列。

主队列是一个串行队列,负责在主线程上执行任务,因此在主队列上执行的任务必须要快速完成,避免阻塞主线程。自定义队列可以是串行队列或并发队列,开发者可以根据自己的需求创建自定义队列。

在GCD中,队列还分为两种执行方式

  • 同步执行
  • 异步执行

同步执行是指任务必须等待前面的任务完成后才能开始执行,任务会阻塞当前线程,因此同步执行适用于需要顺序执行任务的场景。
异步执行是指任务会立即在队列中排队,不会阻塞当前线程,只有当系统资源可用时才会执行任务,因此适用于需要异步执行任务的场景。

GCD 中的任务

在 GCD 中,任务是执行工作的基本单元。可以将任务想象为一些代码块,这些代码块可以在一个队列中按照一定的顺序依次执行,或者可以在多个队列中并行执行。

GCD 中的任务有两种类型:

  • 同步任务
  • 异步任务

同步任务是指在当前线程中执行的任务,会等待任务执行完成之后再继续执行下一个任务。同步任务通常使用串行队列执行。

异步任务是指在后台线程中执行的任务,不会阻塞当前线程,并且可以并发执行多个任务。异步任务通常使用并发队列执行。

GCD中的任务可以用闭包、函数或代码块的形式表示。以下是几个示例:

使用闭包表示一个异步任务:

DispatchQueue.global().async {
    // 异步任务代码
}

使用函数表示一个同步任务:

let queue = DispatchQueue(label: "com.example.serialQueue")
queue.sync {
    // 同步任务代码
}

使用代码块表示一个异步任务:

let queue = DispatchQueue.global(qos: .userInitiated)
queue.async(execute: {
    // 异步任务代码
})

在GCD中,任务可以添加到队列中,然后GCD会自动管理任务的执行。
可以将任务添加到串行队列中,以确保它们按照添加的顺序依次执行。也可以将任务添加到并发队列中,以便它们可以并发执行。

当使用GCD时,需要注意以下几点:

  • 需要选择正确的队列类型(串行队列或并发队列)来执行任务。
  • 需要正确地使用同步和异步任务,以避免死锁和线程阻塞等问题。
  • 需要确保在执行任务时,不会访问共享数据(如全局变量)等造成数据竞争的情况。
  • 需要合理地设置任务的优先级,以确保重要任务优先执行。
  • 需要避免使用过多的线程和队列,以避免资源浪费和性能下降等问题。

GCD 中的执行方法示例

当我们使用GCD创建队列后,可以向队列中添加任务,让它们在队列中依次执行。GCD提供了多种方法来添加任务,常用的包括以下几种:

  • DispatchQueue.sync(execute:): 同步执行一个任务,等待任务执行完成后才会继续执行当前线程。
let queue = DispatchQueue(label: "com.example.myqueue")
queue.sync {
    // 这里执行任务
}
  • DispatchQueue.async(execute:): 异步执行一个任务,不等待任务执行完成就继续执行当前线程。
let queue = DispatchQueue(label: "com.example.myqueue")
queue.async {
    // 这里执行任务
}
  • DispatchQueue.async(group:qosexecute:): 将任务添加到一个任务组中,并异步执行。任务组可以用于等待一组任务完成后再继续执行。
let queue = DispatchQueue(label: "com.example.myqueue")
let group = DispatchGroup()
queue.async(group: group) {
    // 这里执行任务
}
  • DispatchQueue.barrierAsync(execute:): 在并发队列中添加一个屏障任务,保证在屏障任务之前的任务完成后才会执行屏障任务,屏障任务执行完成后才会执行之后的任务。
let queue = DispatchQueue(label: "com.example.myqueue", attributes: .concurrent)
queue.async {
    // 这里执行任务
}
queue.async {
    // 这里执行任务
}
queue.barrierAsync {
    // 这里执行屏障任务
}
queue.async {
    // 这里执行任务
}
queue.async {
    // 这里执行任务
}
  • DispatchWorkItem: 创建一个任务对象,可以用来手动执行或添加到队列中执行。
let workItem = DispatchWorkItem {
    // 这里执行任务
}

let queue = DispatchQueue(label: “com.example.myqueue”)
queue.async(execute: workItem)
以上这些是GCD中添加任务的常用方法,可以根据实际情况选择适合自己的方法。同时,GCD还提供了其他一些方法,如DispatchQueue.global()、DispatchQueue.main等,可以根据不同的需求选择合适的方法。

DispatchQueue.main 和 DispatchQueue.global

DispatchQueue.mainDispatchQueue.global 都是 DispatchQueue 的静态属性,用于获取全局的主队列和后台队列,是 GCD 最常用的两个队列。

DispatchQueue.main 是 iOS 应用程序中的主队列,主要用于在主线程上执行任务,因为在 iOS 应用程序中,只有主线程才能更新 UI 和处理用户交互。所有在 DispatchQueue.main 中执行的任务都将在主线程上执行,如果在主线程中执行较耗时的任务,可能会导致 UI 卡顿,因此需要将这些任务放在后台线程中执行。

DispatchQueue.global 是 GCD 提供的全局队列,它会自动管理线程池,可根据需要动态创建或销毁线程。DispatchQueue.global 提供四种不同的 QoS(Quality of Service)服务质量服务等级:.userInteractive(用户交互)、.userInitiated(用户主动发起)、.default(默认)、.utility(实用工具)。每个等级代表不同的执行优先级和处理资源,可以根据任务的不同特性和优先级,选择合适的 QoS。

当在 DispatchQueue.global 中执行任务时,它们将在后台线程中执行,不会阻塞主线程,因此,执行耗时任务时,应该优先选择在 DispatchQueue.global 中执行。

// 在主队列上执行任务
DispatchQueue.main.async {
    // 更新 UI
}

// 在后台队列中执行任务
DispatchQueue.global().async {
    // 执行耗时操作
}

// 在后台队列中执行带有 QoS 的任务
DispatchQueue.global(qos: .userInitiated).async {
    // 执行用户主动发起的任务
}

注意,使用 DispatchQueue.global 时,我们应该尽量避免阻塞主线程,否则会导致 UI 卡顿,影响用户体验。同时,在使用 DispatchQueue.global 时,也应该合理选择 QoS 等级,以确保任务得到合适的执行优先级和处理资源。

思考樂:调用DispatchQueue.global时,会无限创建新的queque嘛?

不会。调用 DispatchQueue.global 方法时,会返回一个全局共享的 queue。
GCD会根据应用程序的需要和系统资源的状况自动创建一定数量的 queue,并根据需要在这些 queue 中调度任务,而不是无限创建新的queue。这些 queue 通常是线程池,可以被多个任务共享,并且会在任务执行完毕后自动重用。

直接调用DispatchQueue.global 还是自己创建新的Queue好?

在处理并发任务时,可以选择直接使用 DispatchQueue.global,也可以创建自己的 queue。它们各有优缺点,具体取决于应用程序的需求。

直接使用 DispatchQueue.global 的优点是:

  • 无需自己创建 queue,节省了代码的编写和维护成本;
  • 由于全局队列已经被操作系统优化过,因此可以更好地利用系统资源;
  • 队列中的任务可以在不同的线程上执行,因此可以更好地利用多核 CPU。

自己创建新的 queue 的优点是:

  • 可以更好地控制并发操作的执行顺序;
  • 可以限制并发任务的数量,从而避免过度占用系统资源;
  • 可以根据具体应用程序的需求,设置不同的执行优先级和队列类型(串行或并行)。

如何高性能的使用DispatchQueue.global?

使用DispatchQueue.global创建并发队列时,建议使用预定义的QoS类别,如.userInitiated、.utility、.background。这些类别对应了不同的优先级,以及系统默认的并发线程数,可以使得在并发执行任务时更加高效地利用CPU资源。

另外,可以结合使用DispatchWorkItem和DispatchGroup,来实现更高效的任务调度和处理。比如,可以将多个任务包装在一个DispatchGroup中,通过notify(queue:)方法来等待所有任务执行完成,也可以通过wait(timeout:)方法来阻塞当前线程,等待任务执行完成。

在使用DispatchQueue.global时,需要注意任务之间的依赖关系,避免出现死锁或者竞争条件的情况。同时,也需要注意避免在并发执行任务时出现资源抢占的情况,比如多个线程同时对同一个资源进行修改时,需要使用DispatchSemaphore等同步机制来保证资源的正确性。

 类似资料: