- 本文适合有一定Swift并发编程经验的同学
- 这里的并发不包括GCD、操作队列,仅仅包含Swift特有的并发技术
- 要学习Swift的并发模型,首先要理解Swift并发编程的哲学思想,其次要俯瞰整个Swift并发编程的技术模型。
- 这里“哲学”我们可以理解为在解决什么问题。这里的技术模型是指它在技术上的设计思路是什么。
一、简单回顾Swift并发用法
异步编程的例子
func test() async -> Int {
try? await Task.sleep(nanoseconds: 1_000_000_000 * 1)
return 100
}
Task {
let result = await test() // 在Task内可以使用await
print(result)
}
func test2() async -> Int {
try? await Task.sleep(nanoseconds: 1_000_000_000 * 1)
return await test() // 在async函数中可以使用await
}
// let r = await test() 编译器报错,没有在Task或异步上下文中不能调用await
回忆起并发用法了吗?
二、Swift并发哲学(问题)
1. 代码复杂度高,可读性差。
1.1. 典型代表回调地狱。只看下面红色代码部分
// 模拟一个网络请求,返回用户数据
func fetchUserData(completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
completion("User123") // 返回用户ID
}
}
// 获取用户设置
func fetchUserSettings(userID: String, completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
completion("Settings for (userID)") // 返回设置
}
}
// 获取用户偏好设置
func fetchUserPreferences(settings: String, completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
completion("Preferences for (settings)") // 返回偏好设置
}
}
// 回调地狱:调用这些函数并处理回调
fetchUserData { userID in
fetchUserSettings(userID: userID) { settings in
fetchUserPreferences(settings: settings) { preferences in
print("User preferences: (preferences)")
}
}
}
1.2. Swift怎么解决的?
// 模拟一个异步任务,获取用户数据
func fetchUserData() async -> String {
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // 模拟延迟
return "success"
}
// 获取用户设置
func fetchUserSettings(userID: String) async -> String {
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // 模拟延迟
return "success"
}
// 获取用户偏好设置
func fetchUserPreferences(settings: String) async -> String {
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // 模拟延迟
return "success"
}
// 使用 async/await 执行这些任务并简化错误处理
Task {
let userID = await fetchUserData()
let settings = await fetchUserSettings(userID: userID)
let preferences = await fetchUserPreferences(settings: settings)
}
用串行代码的书写方式执行异步任务
2. 错误处理冗长
2.1. 在回调地狱中处理错误。要想学习Swift并发,就必须看懂下面有问题的代码。如果不知道新的并发架构在解决什么问题,也就不能很好的理解后面的模型为什么会设计成这样 ☺
// 模拟一个异步任务,获取用户数据
func fetchUserData(completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
let success = true // 假设成功
if success {
completion(.success("User123"))
} else {
completion(.failure(MyError.networkError))
}
}
}
// 获取用户设置
func fetchUserSettings(userID: String, completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
let success = true // 假设成功
if success {
completion(.success("Settings for (userID)"))
} else {
completion(.failure(MyError.networkError))
}
}
}
// 获取用户偏好设置
func fetchUserPreferences(settings: String, completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global().async {
sleep(1) // 模拟延迟
let success = true // 假设成功
if success {
completion(.success("Preferences for (settings)"))
} else {
completion(.failure(MyError.networkError))
}
}
}
// 错误类型
enum MyError: Error {
case networkError
}
// 调用这些函数并处理回调,层层嵌套
fetchUserData { result in
switch result {
case .success(let userID):
fetchUserSettings(userID: userID) { result in
switch result {
case .success(let settings):
fetchUserPreferences(settings: settings) { result in
switch result {
case .success(let preferences):
print("User preferences: (preferences)")
case .failure(let error):
print("Failed to fetch preferences: (error)")
}
}
case .failure(let error):
print("Failed to fetch settings: (error)")
}
}
case .failure(let error):
print("Failed to fetch user data: (error)")
}
}
2.2. Swift怎么解决的?
// 模拟一个异步任务,获取用户数据
func fetchUserData() async throws -> String {
await Task.sleep(1 * 1_000_000_000) // 模拟延迟
let success = true // 假设成功
if success {
return "User123"
} else {
throw MyError.networkError
}
}
// 获取用户设置
func fetchUserSettings(userID: String) async throws -> String {
await Task.sleep(1 * 1_000_000_000) // 模拟延迟
let success = true // 假设成功
if success {
return "Settings for (userID)"
} else {
throw MyError.networkError
}
}
// 获取用户偏好设置
func fetchUserPreferences(settings: String) async throws -> String {
await Task.sleep(1 * 1_000_000_000) // 模拟延迟
let success = true // 假设成功
if success {
return "Preferences for (settings)"
} else {
throw MyError.networkError
}
}
// 使用 async/await 执行这些任务并简化错误处理
Task {
do {
let userID = try await fetchUserData()
let settings = try await fetchUserSettings(userID: userID)
let preferences = try await fetchUserPreferences(settings: settings)
print("User preferences: (preferences)")
} catch {
print("Error: (error)")
}
}
用串行代码的书写方式执行异步任务,代码复杂度低,便于阅读和维护。
2.3. 数据竞争问题频发出现
可以理解为数据的线程安全问题,老的解决方案是加锁,但容易造成死锁,新的解决方案是actor。
但我认为新的解决方案也不完美,而且在实战中很难得到应用,因为它依赖于async await。如果要用,改造成本太高。
2.4. 小结
最主要的是几个个问题,其他问题都不大,就不再讲了。
这里还要说一下,Swift并发解决了这两个问题,并且提供了新的能力
如果你没有看明白Swift是怎么解决问题的,说明你对await不够理解:
await:表示在这个点 可能会非阻塞式 挂起 当前任务,等待结果,并在之后继续执行
很多人对上面提到的“非阻塞”,还“挂起”有疑问?
- 不阻塞怎么挂起?
- 挂起了当然就阻塞了,怎么可能挂起了还非阻塞
要回答这个问题要从一个现象上说起:在await调用前和调用后分别打印当前线程的id,你会发现线程id有可能不一样,说明任务有可能在两个线程里完成。为什么会出现这样的现象?原因是:
- 怎么挂起:await调用函数,就导致暂停执行当前任务A(非暂停线程),Swift并发框架将任务A和任务A的上下文打包到一个队列里。
- 非阻塞:任务A放到队列后,当前线程继续处理其他任务。当任务A返回(可能是io),空闲线程拿出任务A,恢复任务的上下文,任务A的代码继续执行。
如何还是不明白可以可以看看前面我发的文章:await是怎么实现非阻塞式挂起的
三、Swift并发技术模型
上面聊了Swift并发解决的问题,但一个完整的并发框架只解决问题不行,还要提供完整的能力。所以在讨论模型之前我们首先聊一下并发框架需要提供哪些能力
3.1 并发能力
任务组(TaskGroup):用于并行执行多个相关联的异步任务,并收集它们的结果。
3.2 模型
未完待续,没有写下去的动力,有想看的,下面留言,人多了,再继续总结。