Actor - ShenYj/ShenYj.github.io GitHub Wiki
目前我对 Actor
的了解还不多,只是知道是 Swift 5.5
提供新的结构化并发时同期的新特性
从定义上看是一个 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
在使用上却能和 class
和 struct
一样,俨然是超越了刚刚荣升 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
actor
在隔离实例数据方面非常出色,它提供了一种引用类型,可以在不引入数据竞争的情况下在并发程序中使用。
然而,当需要隔离的数据分散在程序中,或者表示存在于程序之外的一些状态时,将所有的代码和数据集中到一个参与者实例中可能不切实际(比如说,在大型程序中) ,甚至是不可能的(当与那些假设很普遍的系统进行交互时)。
当需要对全局变量、静态属性、跨类型、跨实例进行数据保护, actor
就无能为力了,于是苹果提出了 Global actors
@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
/// 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/await
、actor
、nonisolated
和 isolated
以及 Sendable
等,对此完全陌生的建议一次性抽出点时间,按照以下顺序分别阅读