Swift 中 Actor 的使用

在 Swift 中,Actor 是一种新的并发模型(从 Swift 5.5 引入),用于在多线程环境中提供线程安全的状态管理。它是对传统类的一种扩展,通过将状态隔离到单个线程来防止数据竞争和同步问题。


Actor 的主要特点

  1. 状态隔离:Actor 内的属性和方法是线程安全的,同一时间只能有一个任务访问其内部状态。
  2. 异步访问:外部访问 Actor 的属性或方法时需要使用 await
  3. 可重入:Actor 默认是可重入的,允许其他任务在当前任务挂起时执行。

定义与使用 Actor

定义 Actor

Actor 的定义类似于类,但使用关键字 actor

actor Counter {
    private var count = 0

    func increment() {
        count += 1
    }

    func getCount() -> Int {
        return count
    }
}

使用 Actor

由于 Actor 的状态是隔离的,外部访问其方法或属性时需要使用 await

let counter = Counter()

Task {
    await counter.increment()
    let currentCount = await counter.getCount()
    print("Count: \(currentCount)")
}

Actor 的核心特性与规则

1. 线程安全

Actor 确保内部状态的修改是线程安全的,无需使用锁(LockDispatchQueue)。

actor SafeCounter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -> Int {
        return value
    }
}

let safeCounter = SafeCounter()
Task {
    await safeCounter.increment()
}

2. 外部访问需要异步

Actor 的方法和属性在外部访问时需要通过 await

actor BankAccount {
    private var balance: Double = 0.0

    func deposit(amount: Double) {
        balance += amount
    }

    func getBalance() -> Double {
        return balance
    }
}

let account = BankAccount()
Task {
    await account.deposit(amount: 100.0)
    print("Balance: \(await account.getBalance())")
}

3. 可重入性

Actor 默认是可重入的,意味着当任务挂起时,其他任务可以访问 Actor 的方法或属性。

actor ReentrantActor {
    func task1() async {
        print("Start Task 1")
        try await Task.sleep(nanoseconds: 1_000_000_000) // 模拟异步挂起
        print("End Task 1")
    }

    func task2() {
        print("Task 2 executed")
    }
}

let actor = ReentrantActor()
Task {
    await actor.task1()
}
Task {
    await actor.task2() // 不会被阻塞
}

Actor 的使用场景

  1. 共享资源管理:如计数器、缓存、用户状态等需要线程安全的全局资源。
  2. 异步任务协调:在高并发环境中,Actor 可以用于协调多个异步任务的顺序和状态。
  3. 简化并发代码:替代传统的锁或串行队列,实现线程安全。

Actor 与类的对比

特性 Actor
线程安全性 默认线程安全,防止数据竞争 不提供内置线程安全,需手动管理
状态访问 异步(await 同步
可重入性 默认可重入 不可重入
用途 并发安全的状态管理 通用用途

与 Global Actor 的结合

Swift 提供了 Global Actor,如 @MainActor,用于指定 Actor 的全局作用域,例如保证某些代码在主线程上执行。

@MainActor
class ViewModel {
    var title: String = "Hello"

    func updateTitle(to newTitle: String) {
        title = newTitle
    }
}

在使用时,系统会确保 ViewModel 的所有方法和属性都在主线程上执行。


注意事项

  1. 性能:Actor 是轻量级的,但过度使用可能影响性能。
  2. 数据共享:避免直接通过外部访问 Actor 的状态,应通过方法和封装访问。
  3. 可重入性问题:可重入 Actor 在某些情况下可能导致状态中断,需要仔细设计任务流。

总结

Actor 提供了一种现代化的并发编程模型,通过状态隔离和异步访问,简化了线程安全的实现,适用于高并发环境中的共享状态管理。结合 @MainActor 等特性,还能很好地解决主线程上的并发问题。