返回文章列表

Swift Actor

iOSswift

https://www.hackingwithswift.com/quick-start/concurrency/what-is-an-actor-and-why-does-swift-have-them

Swift 在 5.5 中引入了 Actor。这是一种全新的并发编程模型工具,专为解决多线程环境中可能发生的数据竞争(Data Race)问题而设计。Actor 将共享的、可变的状态封装起来,并确保对其状态的访问是线程安全的,有助于开发者从繁琐的锁管理中解放出来。


1. 什么是 Actor?

Actor 是一种引用类型(Reference Type),与类(Class)类似,但与类不同,Actor 自带线程安全性。
它通过一种称为 “Actor 隔离”(Actor Isolation) 的机制,确保内部状态(属性等)在多线程的环境中只能被一个任务(例如函数调用或属性访问)修改或读取,从而杜绝了并发访问引发的数据竞争问题。

同时,Actor 使用一种任务队列(Task Queue),所有针对 Actor 的操作都会被添加到它内部的任务队列中,并以串行的方式按顺序处理,因此即便有多个并发任务试图访问同一个 Actor,只有一个任务会被执行,其他任务需要依次排队。


2. 为什么需要 Actor?

Swift 中的并发编程在多线程环境下可能面临严重的“数据竞争”问题:

  • 当多个线程同时访问(特别是修改)某些共享资源时,可能导致意想不到的行为(如部分数据被覆盖或丢失)。
  • 为了防止这种问题,传统的做法是使用线程锁(如 DispatchQueueNSLock 等)来手动管理访问顺序,但这会导致代码复杂、难以维护,甚至还可能引入死锁等错误。

Actor 的出现解决了这一问题:

  • 自动保证线程安全,避免手动加锁的复杂性。
  • 通过“异步访问”+“顺序任务调度”机制,防止多线程对同一状态的并发访问。

3. Actor 的特性

(1)线程安全(Thread Safety)

  • Actor 的内部状态默认是线程安全的,只能通过 Actor 自己提供的方法来访问或修改状态。
  • Actor 内置的线程隔离机制确保状态在不需要手动写锁的情况下安全更新。

(2)任务串行化

  • Actor 内部会自动序列化任务的执行,即使有多个线程调用 Actor 的函数或操作其属性,这些任务也会按顺序一个接一个执行。
  • 这种串行化的任务调度机制,是 Actor 内部实现线程安全的核心。

(3)异步访问(Async Access)

  • 与 Actor 交互时,大部分操作需要使用 await(即异步访问),来确保操作任务被串行化处理。
  • Actor 的方法默认是异步的。这种异步访问虽然可能看起来稍复杂,但它换来了线程安全的代码。

(4)与类类似,但更安全

  • 定义 Actor 和定义类类似,只需要使用 actor 关键字替代 class
  • Actor 提供线程安全与并发保护,但与类一样支持继承和引用计数(ARC)。

4. Actor 的适用场景

Actor 非常适合用在并发环境中,特别是以下情况:

  • 共享资源的保护:Actor 能保证多个线程对共享资源(如计数器、缓存、数据存储等)的安全访问。
  • 高并发任务调度:当程序存在复杂的异步操作时,Actor 提供了更优雅的并发模型。
  • 需要异步接口的程序:当你在使用 Swift 的 async/await 新特性时,Actor 能无缝协作,简化异步任务管理。
  • 有严格线程安全需求的项目:通过 Actor,可以确保状态在任何时刻仅由一个线程修改,杜绝数据竞争。

5. 如何定义和使用 Actor

Actor 的定义和使用类似于类,使用 actor 关键字就可以创建一个 Actor。
以下是一个简单例子:

swift
actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        value
    }
}

// 异步调用 Actor 示例
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.getValue())
}

代码解读

  1. actor Counter:通过 actor 声明一个名为 Counter 的 Actor,用于记录计数器的值。
  2. private var value = 0:私有属性 value 封装在 Actor 内,线程安全。
  3. incrementgetValue 方法:提供线程安全的接口操作 value
  4. 外部调用:使用 await 来异步调用 Actor 的方法,确保线程安全及任务串行化。

6. Actor 的优势

  1. 内置线程安全:Actor 自带线程隔离和任务序列化,不需要手动使用锁或队列,极大地减少了编写同步代码的复杂性。
  2. 简化的并发模型:通过 async/await、Task 等新特性结合 Actor,能以更直观的方式管理并发任务。
  3. 防止数据竞争:Actor 内部状态的访问是独占的,同一时间只有一个任务可以操作,从源头杜绝数据竞争。
  4. 增强代码可读性和安全性:Actor 的特性使开发者关注于业务逻辑,而无需纠结并发机制。

7. Actor 的限制(局限)

  1. 默认异步访问

    • Actor 的方法或属性需要使用 await 进行访问或调用,这可能增加代码编写和调用的复杂性。
  2. 潜在的性能开销

    • Actor 为了实现线程安全,内部有任务队列和调度机制,相较于普通类会有额外的性能开销。
    • 如果没有并发任务需求,直接使用普通类可能更高效。
  3. 兼容性问题

    • 将现有的类迁移到 Actor 中,可能需要调整较多代码。
  4. Actor 的线程安全是有限的

    • Actor 只对其自身的内部状态提供线程安全保护。如果有其他全局变量仍在多线程环境中共享,依旧需要额外的保护措施。

8. Actor 在 Swift 并发模型中的角色

Actor 是 Swift 并发模型中重要的组成部分,与 async/awaitTask 协同工作。它通过提供一种简单直观的线程安全方式,解决了传统锁和队列带来的复杂性,让开发者以更高效、更安全的方式实现并发编程。


总结

Actor 的引入重新定义了 Swift 的并发编程规范,是一种专注于线程安全、高效开发的工具。通过内置的任务串行化和线程隔离机制,它大幅度降低了编写并发代码的复杂性,杜绝了数据竞争问题,是并发环境下管理共享资源或处理异步任务的理想选择。然而,它需要异步调用,且有一定性能开销,因此在使用时需要视具体场景权衡其适用性。

Reentrant 是什么(可重入的)

“Reentrant” 是一个术语,常用于计算机科学和编程领域,特别是在多线程编程和并发编程中。它的基本含义是指一个函数或程序可以被中断并安全地再进入,而不会影响其执行结果。

具体来说,reentrant 函数具有以下几个特点:

  1. 无状态性:reentrant 函数不会依赖于任何静态或全局变量,或者如果必须依赖这些变量,它们的访问必须是线程安全的。这意味着这个函数的每次调用都是独立的。

  2. 没有副作用:函数在执行时不会改变外部的状态或变量。这使得在多线程环境中调用这个函数不会影响其他线程的执行。

  3. 可重入:如果一个线程在执行这个函数时被中断(例如,发生了上下文切换),然后另一个线程又调用同样的函数,第二个线程的调用不会影响第一个线程的执行。

举个例子,一个简单的数学计算函数(如计算平方)通常是 reentrant的,因为它只依赖于输入参数,并且不会改变全局状态。然而,如果一个函数维护了一个全局计数器,并且在每次调用时都对其进行了修改,那么这个函数就是非 reentrant 的,因为多个线程可能会同时尝试修改这个共享状态,导致不可预期的行为。

在设计并发程序时,使用 reentrant 函数是一个重要的考虑点,可以帮助避免数据竞争和其他并发相关的问题。

相关文章