什么是Actor
actor
和类一样是引用类型,可以使用构造器,下标,属性,方法。和类不一样的是,actor
在同一时间只允许一个任务访问他的可变状态
,这使得多个任务中的代码可以安全地与同一个actor
实例进行交互。这种安全性由编译器强制实现的,而不是需要开发人员使用诸如锁之类的系统编写样板代码来实现。
swift
actor Company {
let name: String = "某T"
var numberOfEmployees: Int
init(numberOfEmployees: Int) {
self.numberOfEmployees = numberOfEmployees
}
// 实施降本增效计划
func doProfitImprovementPlan(){
layoff(100)
recruit(10)
print(numberOfEmployees)
}
}
Actor 隔离
Actor 隔离是用来保护它的可变状态,通过self
直接引用和修改它的存储属性。
swift
extension Company {
func merge(with company: Company) {
// error: Actor-isolated property 'numberOfEmployees' can not be referenced on a non-isolated actor instance
let number = company.numberOfEmployees
numberOfEmployees += number
}
}
如果 Company
是class
, 这个方法不会报错,但是你需要加入锁机制避免造成数据竞争。
对于Actor
,如果你尝试引用 company.numberOfEmployees
会导致编译错误,因为numberOfEmployees
被隔离在actor
上下文中,只能被self
访问。
一个actor
实例上的所有声明都是默认actor-isolated
。actor-isolated
的声明能自由访问同一个actor
实例的其他actor-isolated
声明。任何不是actor-isolated
的声明称为non-isolated
。non-isolated
的声明同步
访问actor-isolated
的声明。
从actor
外部引用该actor
的actor-isolated
的声明,称为cross-actor
引用。这样的引用有两种方式被允许。
-
引用不可变状态。
这是因为不可变状态不会被修改,所有不会造成数据竞争。
print(company.name)
-
使用异步函数调用
通过异步函数的调用进行的引用。此类异步函数调用会转换成消息。这些消息存储在
actor
的邮箱中,发起异步函数调用的地方可能会被挂起,直到actor
能处理邮箱中对应的消息。actor
一次只能处理一个消息,因此给定的actor
永远不可能有两个并发执行actor
隔离代码的任务。这确保了在actor隔离的可变状态不会发生数据竞争。
改正以上版本
swift
extension Company {
//改为异步函数
func merge(with company: Company) async {
// cross-actor 引用
let number = await company.numberOfEmployees
numberOfEmployees += number
}
}
在actor
外部不能修改它的不可变状态,会导致编译时错误。
swift
Task {
await company.numberOfEmployees += 10
//error: Actor-isolated property 'numberOfEmployees' can not be mutated from a non-isolated context
}
在actor
内部的方法可以定义成异步方法
swift
extension Company {
// 通过self引用可修改可变属性
func recruit(_ numberToRecurit: Int) async {
self.numberOfEmployees += numberToRecurit
}
}
也可以将这个方法定义成同步方法
swift
extension Company {
// 通过self引用可修改可变属性
func recruit(_ numberToRecurit: Int) {
self.numberOfEmployees += numberToRecurit
}
}
我们还可以通过nonisolated
标记非隔离只读属性,这是因为只读属性不会造成数据竞争。
swift
extension Company {
nonisolated var internationalName: String {
return "T"
}
}
可重入性
可重入性是指在一个 actor
处理一个异步方法时,可以暂停当前的任务去处理其他任务,完成后再回到之前的任务继续执行。这种机制允许 actor
更灵活和高效地处理并发任务,提高资源利用率,并且不会有死锁的风险。
但是我们需要注意
的是,可重入性会导致在暂停点之前的状态和暂停点之后的状态不一致。
swift
actor Account {
private var balance: Int
init(balance: Int) {
self.balance = balance
}
func withdraw(amount: Int) async {
// 检查余额
guard balance >= amount else {
return
}
// 可能的暂停点
guard await authenticate() else {
return
}
// 此时我们不能保证在暂停点后能够取钱,我们必须再检查一次余额。
guard balance >= amount else {
return
}
balance -= amount
print("balance: \(balance)")
}
func authenticate() async -> Bool {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return true
}
}
// 客户端调用
let account = Account(balance: 5)
for _ in 0..<12{
Task {
await account.withdraw(amount:1)
}
}
考察账户程序的withdraw(:)
函数,如果我们没有在authenticate()
之后检查余额,很可能会造成余额为负数的情况。这是因为当一个任务执行运行至暂停点时,另外一个任务执行异步函数也来到暂停点,由于之前authenticate()
还没有处理完,此时新的任务也将在暂停点等待,然后前一个任务在暂停点恢复取款,接着新的任务在暂停点恢复取款,此时如果没有判断是否可以继续取款,有可能会造成余额为负数的情况。
@globalActor
在 Swift 中,Global Actors 是用于确保某些代码片段总是在同一个特定的执行上下文中运行的全局抽象。Global Actors 提供了一种简单的方法来确保线程安全性和同步性,特别是在处理共享状态和资源时。在任何使用 global actor 属性的地方,你都将通过共享的 actor 实例确保同步,以确保对声明的互斥访问。
swift
@globalActor
actor DataManagerActor {
static let shared = DataManagerActor()
}
@DataManagerActor
class Counter {
private var value = 0
func increment() {
value += 1
print(value)
}
}
func doCount() async {
let counter = await Counter()
for _ in 0..<6 {
Task {
await counter.increment()
}
}
}
Task {
await doCount()
}
@MainActor
@MainActor
是 Swift Concurrency 中的一个关键特性,它保证被标注的代码在主线程上运行。这个特性对于需要在主线程上执行的操作(如更新用户界面)非常重要。
-
保证在主线程上执行:所有标注了
@MainActor
的代码都将确保在主线程上执行。这对于 UI 更新和其他需要在主线程上进行的任务是必要的。主线程是应用程序的主执行线程,通常处理用户输入和 UI 更新。如果在后台线程上执行 UI 更新操作,会导致不可预测的行为和崩溃。 -
线程安全:通过使用
@MainActor
,可以确保代码在一个单一线程上执行,从而避免多线程环境下的竞争条件和数据竞态。 -
简化代码:使用
@MainActor
可以使代码更加简洁和易读,因为不需要手动切换到主线程。例如,在没有@MainActor
的情况下,需要显式地使用DispatchQueue.main.async
来切换到主线程。
swift
//保证运行在主线程
@MainActor func updateUI(text: String) {
self.titleLabel.stringValue = text
}
Task {
let data = Task {
await fetchData(from:"www.apple.com")
}
let result = await data.value
//保证运行在主线程
await MainActor.run {
self.titleLabel.stringValue = result
}
}
结语
希望这篇文章能帮助你更好地理解和运用 Swift 的 actor 模型,提高你的并发编程能力。如果你有任何问题或建议,欢迎在评论区留言讨论。祝你编程愉快!