Actor - ShenYj/ShenYj.github.io GitHub Wiki

Actor

目前我对 Actor 的了解还不多,只是知道是 Swift 5.5 提供新的结构化并发时同期的新特性

Actor和actor

从定义上看是一个 protocol

/// Common protocol to which all actors conform.
///
/// The `Actor` protocol generalizes over all actor types. Actor types
/// implicitly conform to this protocol.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol Actor : AnyObject, Sendable {

    /// Retrieve the executor for this actor as an optimized, unowned
    /// reference.
    ///
    /// This property must always evaluate to the same executor for a
    /// given actor instance, and holding on to the actor must keep the
    /// executor alive.
    ///
    /// This property will be implicitly accessed when work needs to be
    /// scheduled onto this actor.  These accesses may be merged,
    /// eliminated, and rearranged with other work, and they may even
    /// be introduced when not strictly required.  Visible side effects
    /// are therefore strongly discouraged within this property.
    nonisolated var unownedExecutor: UnownedSerialExecutor { get }
}

其主要的功能:

actor 中的变量在任意时间段内只会被一个线程获取,这也就确保了 actor 在并发环境下的安全

从其协议的继承链中可见, 这正是 Sendable 所提供的一种特性,Sendable 的描述空空如也,并没有暴露什么内容

/// The Sendable protocol indicates that value of the given type can
/// be safely used in concurrent code.
public protocol Sendable {
}

燃鹅,Actor 在使用上却能和 classstruct 一样,俨然是超越了刚刚荣升 Swift 三大金刚之 enum 的地位

  • 示例代码

    actor SafeCollector {
        var deck: Set<String>
    
        init(deck: Set<String>) {
            self.deck = deck
        }
    
        func send(card selected: String, to person: SafeCollector) async -> Bool {
            guard deck.contains(selected) else { return false }
    
            deck.remove(selected)
            await person.transfer(card: selected)
            return true
        }
    
        func transfer(card: String) {
            deck.insert(card)
        }
    }

与其他 Swift 类型一样,actor 可以具有初始化程序、方法、属性和下标。它们可以扩展并符合协议,是泛型的,并且可以与泛型一起使用

Swift includes classes, which provide a mechanism for declaring mutable state that is shared across the program. Classes, however, are notoriously difficult to correctly use within concurrent programs, requiring error-prone manual synchronization to avoid data races. We want to provide the ability to use shared mutable state while still providing static detection of data races and other common concurrency bugs.

由此可见,这或许是苹果相对于之前为了数据安全采取的线程同步机制做出的改良

截至目前 Swift 5.6 版本

  • actor 还不支持继承(早期版本曾支持,但衡量成本高于可用性后移除了)
  • class 一样属于引用类型
  • 所有的 actor 隐式的遵循了 Actor 的协议,其他的类型不能使用这个协议

Actor 通过所谓数据隔离 (Actor isolation) 的方式确保数据安全,其实现原理是 Actor 内部维护了一个串行队列 (mailbox),所有涉及数据安全的外部调用都要入队,即它们都是串行执行的

An actor processes the messages in its mailbox sequentially, so that a given actor will never have two concurrently-executing tasks running actor-isolated code. This ensures that there are no data races on actor-isolated mutable state, because there is no concurrency in any code that can access actor-isolated state

Global actors

actor 在隔离实例数据方面非常出色,它提供了一种引用类型,可以在不引入数据竞争的情况下在并发程序中使用。

然而,当需要隔离的数据分散在程序中,或者表示存在于程序之外的一些状态时,将所有的代码和数据集中到一个参与者实例中可能不切实际(比如说,在大型程序中) ,甚至是不可能的(当与那些假设很普遍的系统进行交互时)。

当需要对全局变量、静态属性、跨类型、跨实例进行数据保护, actor 就无能为力了,于是苹果提出了 Global actors

@MainActor

@MainActor 的主要动机是将参与者模型应用于只能由主线程访问的状态和操作, 使用 @MainActor 来标记只能在主线程上访问的属性和方法

  • 例如

    class NewDataController {
        @MainActor func save() {
            print("Saving data…")
        }
    }

    使用 @MainActor,就可以保证总是在主线程上调用 save() 方法,等同于 DispatchQueue.main 运行它一样

  • 除此之外,还可以用来修饰属性,与 Actor 一样,需要配合 async/await 来使用

    @MainActor var globalTextSize: Int
    
    @MainActor func increaseTextSize() { 
    globalTextSize += 2   // okay: 
    }
    
    func notOnTheMainActor() async {
    globalTextSize = 12  // error: globalTextSize is isolated to MainActor
    increaseTextSize()   // error: increaseTextSize is isolated to MainActor, cannot call synchronously
    await increaseTextSize() // okay: asynchronous call hops over to the main thread and executes there
    }

@GlobalActor

先看源码

  • GlobalActor

    /// A type that represents a globally-unique actor that can be used to isolate
    /// various declarations anywhere in the program.
    ///
    /// A type that conforms to the `GlobalActor` protocol and is marked with
    /// the `@globalActor` attribute can be used as a custom attribute. Such types
    /// are called global actor types, and can be applied to any declaration to
    /// specify that such types are isolated to that global actor type. When using
    /// such a declaration from another actor (or from nonisolated code),
    /// synchronization is performed through the shared actor instance to ensure
    /// mutually-exclusive access to the declaration.
    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    public protocol GlobalActor {
    
        /// The type of the shared actor instance that will be used to provide
        /// mutually-exclusive access to declarations annotated with the given global
        /// actor type.
        associatedtype ActorType : Actor
    
        /// The shared actor instance that will be used to provide mutually-exclusive
        /// access to declarations annotated with the given global actor type.
        ///
        /// The value of this property must always evaluate to the same actor
        /// instance.
        static var shared: Self.ActorType { get }
    
        /// The shared executor instance that will be used to provide
        /// mutually-exclusive access for the global actor.
        ///
        /// The value of this property must be equivalent to `shared.unownedExecutor`.
        static var sharedUnownedExecutor: UnownedSerialExecutor { get }
    }
    
    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    extension GlobalActor {
    
        /// The shared executor instance that will be used to provide
        /// mutually-exclusive access for the global actor.
        ///
        /// The value of this property must be equivalent to `shared.unownedExecutor`.
        public static var sharedUnownedExecutor: UnownedSerialExecutor { get }
    }
  • MainActor

    /// A singleton actor whose executor is equivalent to the main
    /// dispatch queue.
    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    @globalActor final public actor MainActor : GlobalActor {
    
        /// The shared actor instance that will be used to provide mutually-exclusive
        /// access to declarations annotated with the given global actor type.
        ///
        /// The value of this property must always evaluate to the same actor
        /// instance.
        public static let shared: MainActor
    
        /// Retrieve the executor for this actor as an optimized, unowned
        /// reference.
        ///
        /// This property must always evaluate to the same executor for a
        /// given actor instance, and holding on to the actor must keep the
        /// executor alive.
        ///
        /// This property will be implicitly accessed when work needs to be
        /// scheduled onto this actor.  These accesses may be merged,
        /// eliminated, and rearranged with other work, and they may even
        /// be introduced when not strictly required.  Visible side effects
        /// are therefore strongly discouraged within this property.
        @inlinable nonisolated final public var unownedExecutor: UnownedSerialExecutor { get }
    
        /// The shared executor instance that will be used to provide
        /// mutually-exclusive access for the global actor.
        ///
        /// The value of this property must be equivalent to `shared.unownedExecutor`.
        @inlinable public static var sharedUnownedExecutor: UnownedSerialExecutor { get }
    
        @inlinable nonisolated final public func enqueue(_ job: UnownedJob)
    
        /// The type of the shared actor instance that will be used to provide
        /// mutually-exclusive access to declarations annotated with the given global
        /// actor type.
        public typealias ActorType = MainActor
    }
    
    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    extension MainActor {
    
        /// Execute the given body closure on the main actor.
        public static func run<T>(resultType: T.Type = T.self, body: @MainActor @Sendable () throws -> T) async rethrows -> T where T : Sendable
    }
    • sharedUnownedExecutor 默认提供了 extension 实现
    • MainActor 中对 shared 属性进行了实现

MainActor 是苹果默认提供的一个 GlobalActor 的一个具体实现,虽然没看到 MainActor 是怎么保证你在主线程执行任务的,但是从源码定义上可以掌握到的信息是: 归根结底还是基于 GlobalActor

并且除了直接使用 @MainActor 的方式,还可以通过 MainActor.run 的方式在主线程上执行一段代码

资料

关于Swift 5.5提出结构化并发方案后,产生的相关关键字有async/awaitactornonisolatedisolated 以及 Sendable等,对此完全陌生的建议一次性抽出点时间,按照以下顺序分别阅读

⚠️ **GitHub.com Fallback** ⚠️