2. Swift 并发初步
一些基本概念
同步和异步
- 同步(Synchronous):函数调用后立即执行所有操作,直到任务完成才返回结果,阻塞当前线程。适合耗时极短、必须立即完成的操作。
- 异步(Asynchronous):函数调用后不等待任务完成,立即返回;任务在后台执行,完成后通过回调、
async/await等方式通知结果,不阻塞当前线程。适合网络请求、文件读写、复杂计算等耗时操作。
import Foundation
// 同步函数:阻塞当前线程直到返回
func synchronousLoadData(from url: URL) -> Data? {
return try? Data(contentsOf: url)
}
// 异步函数(async/await):不阻塞当前线程
func asynchronousLoadData(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 使用示例
func testSyncAsync() {
let url = URL(string: "https://example.com")!
// 同步调用:会阻塞主线程
let syncData = synchronousLoadData(from: url)
print("同步数据长度:\(syncData?.count ?? 0)")
// 异步调用:必须在异步上下文中执行
Task {
do {
let asyncData = try await asynchronousLoadData(from: url)
print("异步数据长度:\(asyncData.count)")
} catch {
print("请求失败:\(error)")
}
}
}
串行和并行
- 串行(Serial):多个任务按严格的先后顺序执行,前一个任务完全完成后,后一个才开始。同一时间只有一个任务在运行。
- 并行(Parallel):多个任务同时执行,依赖多核CPU硬件支持。同一时间有多个任务在不同CPU核心上运行。
// 模拟耗时任务
func work(_ number: Int) async {
print("开始任务 \(number)")
try? await Task.sleep(for: .seconds(1))
print("完成任务 \(number)")
}
// 串行执行:总耗时约3秒
func serialExecution() async {
print("=== 串行执行 ===")
await work(1)
await work(2)
await work(3)
}
// 并行执行:总耗时约1秒
func parallelExecution() async {
print("=== 并行执行 ===")
async let w1 = work(1)
async let w2 = work(2)
async let w3 = work(3)
await w1, w2, w3
}
// 运行测试
Task {
await serialExecution()
await parallelExecution()
}
并发是什么
- 并发(Concurrency):在同一时间段内处理多个任务的能力。宏观上看起来多个任务同时进行,微观上分为两种实现:
- 单核CPU:通过时间片轮转切换任务,实现"伪并行"
- 多核CPU:部分任务真正并行执行
- 核心目标:提高CPU、内存等资源利用率,提升程序响应速度。
- 并发 vs 并行:并行是并发的一种实现方式;并发关注"任务的调度和管理",并行关注"任务的同时执行"。
异步函数
核心概念
- 异步函数:用
async标记的函数,内部可以包含暂停点(await)。执行到暂停点时会暂时放弃当前线程,让线程执行其他任务,等待异步操作完成后再恢复执行。 await:标记异步函数的暂停点,必须在async函数或async闭包中使用。- 续体(Continuation):异步函数暂停时保存的执行状态(包括局部变量、程序计数器等),用于恢复执行。
- 回调转异步:使用
withCheckedContinuation/withUnsafeContinuation将传统基于回调的API转换为async/await风格。 - 异步序列:表示一系列未来会产生的值,通过
for try await迭代,对应同步世界的Sequence。
代码示例
- 异步函数的链式调用
// 定义异步函数
func fetchUser(id: Int) async throws -> String {
try await Task.sleep(for: .seconds(1)) // 模拟网络请求
return "用户\(id)"
}
func fetchUserPosts(userId: String) async throws -> [String] {
try await Task.sleep(for: .seconds(1))
return ["帖子1", "帖子2", "帖子3"]
}
// 链式调用异步函数
func loadUserAndPosts() async {
do {
let user = try await fetchUser(id: 1)
let posts = try await fetchUserPosts(userId: user)
print("用户:\(user),帖子:\(posts)")
} catch {
print("加载失败:\(error)")
}
}
Task {
await loadUserAndPosts()
}
- 传统回调转async/await
// 传统基于回调的API
func legacyFetchData(completion: @escaping (Data?, Error?) -> Void) {
URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { data, _, error in
completion(data, error)
}.resume()
}
// 转换为async/await风格
func modernFetchData() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
legacyFetchData { data, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data {
continuation.resume(returning: data)
}
}
}
}
- 异步序列与AsyncStream
// 使用AsyncStream创建计时器异步序列
let timerStream = AsyncStream<Date> { continuation in
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
continuation.yield(Date())
}
// 序列结束时清理资源
continuation.onTermination = { _ in
timer.invalidate()
}
}
// 迭代异步序列
Task {
for await date in timerStream.prefix(3) {
print("当前时间:\(date)")
}
print("计时器序列结束")
}
结构化并发
核心概念
- 结构化并发:并发任务具有明确的层级关系和生命周期,父任务必须等待所有子任务完成后才能结束,取消操作会自动向下传递给所有子任务。
- 结构化任务创建方式:
async let:异步绑定,适合固定数量的并发任务TaskGroup:任务组,适合动态数量的并发任务
- 非结构化任务:
Task.init(继承当前上下文)和Task.detached(完全独立),生命周期不与父任务绑定,需要手动管理取消。 - 协作式取消:任务不会被强制终止,需要主动检查
Task.isCancelled或调用Task.checkCancellation()来响应取消。
代码示例
async let异步绑定
// 同时获取用户和帖子,总耗时约1秒而非2秒
func fetchUserAndPostsParallel() async throws -> (String, [String]) {
async let user = fetchUser(id: 1)
async let posts = fetchUserPosts(userId: "用户1")
return try await (user, posts)
}
Task {
do {
let (user, posts) = try await fetchUserAndPostsParallel()
print("用户:\(user),帖子数:\(posts.count)")
} catch {
print("获取失败:\(error)")
}
}
TaskGroup动态任务组
// 动态并发获取多个用户
func fetchMultipleUsers(ids: [Int]) async throws -> [String] {
try await withThrowingTaskGroup(of: String.self) { group in
// 为每个ID添加子任务
for id in ids {
group.addTask {
try await fetchUser(id: id)
}
}
// 收集所有子任务结果
var users: [String] = []
for try await user in group {
users.append(user)
}
return users
}
}
Task {
let users = try await fetchMultipleUsers(ids: [1, 2, 3])
print("所有用户:\(users)")
}
- 任务取消处理
func longRunningTask() async {
for i in 1...10 {
// 主动检查任务是否被取消
guard !Task.isCancelled else {
print("任务被取消,提前退出")
return
}
print("执行步骤 \(i)")
try? await Task.sleep(for: .seconds(0.5))
}
print("任务全部完成")
}
Task {
let task = Task {
await longRunningTask()
}
// 2秒后取消任务
try await Task.sleep(for: .seconds(2))
task.cancel()
}
actor 模型和数据隔离
核心概念
- 数据竞争:多个线程同时读写同一块可变内存,导致未定义行为(崩溃、数据错乱等)。
- actor:Swift原生提供的并发安全类型,通过隔离域保护内部可变状态,同一时间只有一个任务能访问actor的隔离域。
- 跨actor调用:访问actor的属性或方法必须使用
await,会触发"actor跳跃"(切换到actor的专属执行器)。 - 全局actor:如
@MainActor,将代码隔离到主线程执行,专门用于UI相关操作。 - actor可重入性:actor的异步方法在
await暂停时,允许其他任务进入actor执行,可能导致状态在暂停前后发生变化。
代码示例
- 基础actor使用(解决数据竞争)
// 定义actor:保护内部计数器
actor SafeCounter {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
count
}
}
Task {
let counter = SafeCounter()
// 1000个并发任务同时递增,无数据竞争
await withTaskGroup(of: Void.self) { group in
for _ in 1...1000 {
group.addTask {
await counter.increment()
}
}
}
let finalCount = await counter.getCount()
print("最终计数:\(finalCount)") // 稳定输出1000
}
@MainActor主线程隔离
import UIKit
// 整个类的所有成员都隔离在主线程
@MainActor
class UserViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loadUserName()
}
func loadUserName() {
Task {
// 后台加载数据
let user = try await fetchUser(id: 1)
// 自动在主线程更新UI,无需手动切换
nameLabel.text = user
}
}
}
- actor可重入性问题与解决
actor UserLoader {
private var cachedUser: String?
private var isLoading = false
// 问题版本:多个任务同时调用会重复请求
func badLoadUser() async -> String {
if let user = cachedUser {
return user
}
// await暂停时,其他任务可以进入actor
try? await Task.sleep(for: .seconds(1))
let user = "用户1"
cachedUser = user
return user
}
// 修复版本:添加加载状态避免重复请求
func goodLoadUser() async -> String {
if let user = cachedUser {
return user
}
guard !isLoading else {
// 等待其他加载任务完成
while isLoading {
try? await Task.sleep(for: .milliseconds(100))
}
return cachedUser!
}
isLoading = true
defer { isLoading = false }
try? await Task.sleep(for: .seconds(1))
let user = "用户1"
cachedUser = user
return user
}
}
小结
本章是Swift并发编程的基石,核心围绕**"如何安全、高效地处理多任务"**展开:
- 基础概念:明确了同步/异步(执行时机)、串行/并行(执行方式)、并发(任务调度)的本质区别,建立并发编程的思维模型。
- 异步函数:通过
async/await彻底解决了传统回调地狱问题,让异步代码拥有和同步代码相似的可读性;异步序列则统一了"一系列未来值"的处理方式。 - 结构化并发:通过层级化的任务管理,自动处理任务生命周期、错误传播和取消操作,从根本上避免了传统并发中的资源泄漏、任务逃逸等问题。
- actor模型:从语言层面彻底解决了数据竞争问题,通过隔离域保证可变状态的安全访问;
@MainActor等全局actor则简化了主线程UI操作的安全管理。
这些概念相互配合,构成了Swift并发编程的完整框架,让开发者能够以更简洁、更安全的方式编写高性能的并发代码。
需要我把这些知识点整理成一份可打印的速查表,方便你随时查阅吗?