1、GCD好处
- GCD可用于多核的并行运算
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度线程、销毁线程)
- 程序员只需告诉GCD想要执行什么任务,不需要编写任何线程管理代码
2、GCD任务和队列
- 同步执行(sync)
- 异步执行(async)
- 串行队列(Serial Dispatch Queue):
- 并发队列(Concurrent Dispatch Queue):
3、GCD的使用步骤
3.1队列的创建方法/获取方法
- 可以使用dispatch_queue_creat来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空,Dispatch Queue的名称推荐使用应用程序ID这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CURRENT表示并发队列。
- 对于串行队列,GCD提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)。
- 对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)。
3.2任务的创建方法
4、GCD的基本使用
4.1同步执行 + 并发队列
- 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行 不具备开启新线程的能力)。
- 同步任务需要等待队列的任务执行结束
- 任务按顺序执行的,按顺序执行的原因:虽然 并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建多线程,只有当前这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时执行。
4.2 异步执行 + 并发队列
- 可以开启多个线程,任务交替(同时)执行。
- 除了当前线程(主线程),系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行 具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。
- 所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)
4.3 同步执行 + 串行队列
- 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。
- 所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
- 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
4.4异步执行 + 串行队列
- 开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。
- 所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
- 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
- 主队列:GCD自带的一种特殊的串行队列
4.5 同步执行 + 主队列
- 互相等待卡住不可行
- 在主线程中使用 同步执行 + 主队列,追加到主线程的任务1、任务2、任务3不再执行了,而且,syncMain——end也没有打印,在xcode 9 上还会报崩溃。这是为什么呢?
- 不会开启新线程,执行完一个任务,在执行下一个任务
- 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有的主队列任务,都会放在主线程中执行)。
- 所有任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
- 任务是按顺序执行的(主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。
4.6 异步执行 + 主队列
- 只在主线程中执行任务,执行完一个任务,再执行下一个任务。
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行 具备开启新线程的能力,但因为是主队列,所以所有任务都在主线程中)。
- 所有任务都是在打印syncCncurren——begin和syncConcurren——end 之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
- 任务是按顺序执行的(因为主队列是 串行队列,每次只有一个任务呗执行,任务一个接一个按顺序)。
5.GCD线程间的通信
GCD 线程间通信方法
GCD 本身不提供直接的线程间通信机制,但我们可以利用一些技巧和工具来实现线程间的有效通信:
1. 主队列与全局队列结合:
-
在后台线程执行耗时操作,完成后在主队列更新 UI。
-
使用 DispatchQueue.main.async 将更新 UI 的代码块提交到主队列执行。
DispatchQueue.global().async {
// 在后台线程执行耗时操作
...
DispatchQueue.main.async {
// 更新 UI 界面
}
}
content_copyUse code with caution.Swift
2. 使用 DispatchGroup:
-
将多个异步任务添加到 DispatchGroup 中,等待所有任务完成。
-
使用 notify 方法在所有任务完成时执行回调函数。
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
// 任务 1
...
group.leave()
}
group.enter()
DispatchQueue.global().async {
// 任务 2
...
group.leave()
}
group.notify(queue: .main) {
// 所有任务完成后执行
}
content_copyUse code with caution.Swift
3. 使用 DispatchSemaphore:
-
利用信号量来控制线程之间的同步和互斥。
-
一个线程可以等待信号量,另一个线程可以发送信号量,从而实现线程间的协作。
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
// 执行任务
...
semaphore.signal() // 发送信号
}
semaphore.wait() // 等待信号
// 继续执行后续操作
4. 使用共享内存:
-
在多个线程之间共享数据,可以使用原子操作 (atomic operations) 或线程安全的集合类 (例如 ConcurrentDictionary) 来保证数据的一致性。
5. 使用第三方库:
-
可以使用 PromiseKit、RxSwift 等第三方库来简化异步编程和线程间通信。
注意事项:
-
避免数据竞争:使用同步机制或线程安全的集合类来保护共享数据。
-
避免死锁:确保线程获取锁的顺序一致,或使用超时机制。
-
主线程更新 UI:所有 UI 更新操作必须在主线程执行。
总结:
GCD 可以与其他机制结合,实现线程间的有效通信,例如主队列与全局队列结合、DispatchGroup、DispatchSemaphore、共享内存和第三方库等。 开发者需要根据具体需求选择合适的方法,并注意线程安全和性能优化。
6.GCD的其他方式
6.1 GCD栅栏方法:dispatch_barrier_async
- 我们有时候需要异步执行两组操作,而且第一组操作执行完之后,才能执行第二组操作。这样我们就需要一个相对于栅栏一样的一个方法将两组异步执行的操作给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列中并开始执行。具体如下图所示:
- 在执行完栅栏前面你的操作之后,才可以执行栅栏操作,最后再执行栅栏后边的操作。
6.2 GCD延时执行方法:dispatch_after
6.3 GCD一次性代码 (只执行一次):dispatch_once
- 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once 函数。使用dispatch_once 函数能保证某段代码在程序执行过程中只被执行1次,并且即使在使用多线程的环境下,dispatch_once 也可以保证线程安全。
6.4 GCD 快速迭代方法:dispatch_apply
- 通常我们会用for循环遍历,但是 GCD 给我们提供了快速迭代的函数 dispatch_apply 。 dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
6.5 GCD队列组:dispatch_group
- 调用队列组的dispatch_group_async 先把任务放到队列中,然后将队列放入队列组。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。
- 调用队列组的dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
- 监听group 中任务的完成情况,当所有的任务都执行完,追加任务到group 中,并执行任务。
- 暂停当前线程(阻塞当前线程),等待指定的group中的任务执行完成后,才会往下继续执行。
- Dispatch_group_enter 标志着一个任务追加到group,执行一次,相当于group中未执行的任务数+1
- dispatch_group_leave 标志着一个任务离开了group,执行一次,相当于group中未执行的任务书-1.
- 当group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait 解除阻塞,以及执行追加到dispatch_group_notify中的任务。
6.6 GCD信号量:dispatch_semaphore
- dispatch_semaphore_creat:创建一个semaphore并初始化信号的总量
- dispatch_semaphore_signal:发送一个信号,让信号总量加1
- dispatch_semapore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程,否则就可以正常执行。
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
- semaphore——end 是在执行完 number = 100;之后才打印的。而且输出结果number 为100.
7. Swift 中的多线程技术:并发编程的利器
Swift 主要通过 Grand Central Dispatch (GCD) 和 Operation Queues 实现多线程编程。 以下介绍这两种技术的使用方法:
1. Grand Central Dispatch (GCD):
- 创建队列:
// 串行队列
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
// 并发队列
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
// 主队列 (串行)
let mainQueue = DispatchQueue.main
content_copy
Use code with caution.
Swift
- 提交任务:
// 异步执行任务
concurrentQueue.async {
// 在后台线程执行的任务
print("Task running in background thread")
}
// 同步执行任务
serialQueue.sync {
// 在当前线程执行的任务
print("Task running in current thread")
}
// 在主线程执行任务
DispatchQueue.main.async {
// 更新 UI 等操作
print("Task running in main thread")
}
content_copy
Use code with caution.
Swift
2. Operation Queues:
- 创建队列:
let operationQueue = OperationQueue()
content_copy
Use code with caution.
Swift
- 创建操作:
let operation = BlockOperation {
// 要执行的任务
print("Operation running")
}
content_copy
Use code with caution.
Swift
- 添加操作到队列:
operationQueue.addOperation(operation)
content_copy
Use code with caution.
Swift
- 设置依赖关系:
let operation2 = BlockOperation {
// ...
}
operation2.addDependency(operation) // operation2 依赖于 operation
operationQueue.addOperations([operation, operation2], waitUntilFinished: false)
content_copy
Use code with caution.
Swift
区别和选择:
- GCD: 更轻量级,适用于简单的并发任务。
- Operation Queues: 更高级,可以管理任务之间的依赖关系,支持取消、暂停、恢复任务,以及 KVO 观察任务状态,适用于更复杂的并发场景。
注意事项:
- 线程安全: 访问共享资源时,需要注意线程安全问题,避免数据竞争。 可以使用 DispatchQueue 的同步方法或互斥锁等机制来保证线程安全。
- 主线程更新 UI: 更新 UI 操作必须在主线程执行,可以使用 DispatchQueue.main.async 将任务提交到主线程。
总结:
Swift 提供了 GCD 和 Operation Queues 两种多线程技术,开发者可以根据具体需求选择合适的技术,并注意线程安全和性能优化。
8. iOS 多线程技术:并发编程的选项和区别
iOS 开发中,多线程技术可以提升应用的性能和响应速度,改善用户体验。 主要有以下几种常见的多线程技术,它们之间存在一些区别:
1. Grand Central Dispatch (GCD):
- 特点: 底层 C 语言 API,轻量级,高效。
- 使用方式: 创建队列 (串行或并发),将任务 (block) 提交到队列中执行。
- 优点: 简单易用,性能优越,由系统管理线程生命周期。
- 缺点: 控制粒度较粗,无法直接控制线程生命周期和状态。
2. Operation Queues:
- 特点: 基于 GCD 构建的面向对象 API,更高级别的抽象。
- 使用方式: 创建 Operation 对象封装任务,设置依赖关系,将其添加到队列中执行。
- 优点: 易于管理任务之间的依赖关系,支持取消、暂停、恢复任务,可以 KVO 观察任务状态。
- 缺点: 比 GCD 复杂一些,性能可能略低于 GCD。
3. Threads:
- 特点: 最底层的线程 API,提供对线程的完全控制。
- 使用方式: 创建 Thread 对象,实现线程执行函数,启动线程。
- 优点: 完全控制线程生命周期和行为,可以进行更细粒度的控制。
- 缺点: 使用复杂,需要手动管理线程同步和资源竞争问题,容易出错。
4. NSThread (Objective-C):
- 特点: Objective-C 中用于创建和管理线程的类,功能类似于 Swift 中的 Thread 类。
- 使用方式: 创建 NSThread 对象,实现线程执行方法,启动线程。
- 注意: 在 Swift 中,通常使用 GCD 或 Operation Queues,而不是 NSThread。
选择哪种多线程技术:
- 简单并发任务: GCD 是首选,简单易用且高效。
- 复杂任务管理,需要依赖关系和状态控制: Operation Queues 更合适。
- 需要完全控制线程: 使用 Threads,但需注意线程安全问题。
区别总结:
技术 | 特点 | 优点 | 缺点 |
GCD | 轻量级,高效 | 简单易用,性能优越 | 控制粒度粗,无法直接控制线程 |
Operation Queues | 面向对象,更高级抽象 | 易于管理任务,支持更多控制选项 | 比 GCD 复杂,性能略低 |
Threads | 底层,完全控制 | 精确控制线程生命周期和行为 | 使用复杂,易出错 |
无论使用哪种技术,都需要注意线程安全和资源竞争问题,确保代码的正确性和稳定性。