actor 是线程安全的么?

actor 是线程安全的么?

The Actor Reentrancy Problem in Swift

不是的。考虑reentrance的情况

suspension point是否在actor里,如果有的话,那么在并发的两个task,就有可能会发生重入(Reentrance)的情况。

举个例子:

swift 复制代码
actor BankAccount {
    
    private var balance = 1000
    
    func withdraw(_ amount: Int) async {
        
        print("🤓 Check balance for withdrawal: (amount)")
        
        guard canWithdraw(amount) else {
            print("🚫 Not enough balance to withdraw: (amount)")
            return
        }
        
        guard await authorizeTransaction() else {
            return
        }
        
        print("✅ Transaction authorized: (amount)")
        
        balance -= amount
        
        print("💰 Account balance: (balance)")
    }
    
    private func canWithdraw(_ amount: Int) -> Bool {
        return amount <= balance
    }
    
    private func authorizeTransaction() async -> Bool {
        
        // Wait for 1 second
        try? await Task.sleep(nanoseconds: 1 * 1000000000)
        
        return true
    }
}
​
let account = BankAccount()
​
Task {
    await account.withdraw(800)
}
​
Task {
    await account.withdraw(500)
}

我们可以想象一下,这个会输出什么结果?3,2,1

乍一看,我们会想,先执行完await account.withdraw(800),然后剩余200,然后执行await account.withdraw(500),发现余额不足,不执行。

但是结果如下:

yaml 复制代码
🤓 Check balance for withdrawal: 800
🤓 Check balance for withdrawal: 500
✅ Transaction authorized: 800
💰 Account balance: 200
✅ Transaction authorized: 500
💰 Account balance: -300

你看,余额变成了-300,这明显与我们的认知不符合。那么为什么会出现的结果呢,其实是因为,actor只是能保证不出现数据竞争,但是我们的withdraw方法都会在authorizeTransaction挂起,那么也就是意味着,我们并没有在一个task执行完,才去执行另一个。也就是说,它并不会去判断挂起的时候,可变的状态还是保持你刚进入task的状态,可能有点拗口,那拿这个例子举例,就是actor不会保证,balance会自动修改为task1执行完后的值,也就是200。所以我们在做balance -= amount操作的时候,需要再判断一下,余额是否满足条件,这也是apple建议我们做的,即始终保持actor的状态变更是同步的。

即:

swift 复制代码
func withdraw(_ amount: Int) async {
    
    // Perform authorization before check balance
    guard await authorizeTransaction() else {
        return
    }
    print("✅ Transaction authorized: (amount)")
    
    print("🤓 Check balance for withdrawal: (amount)")
    guard canWithdraw(amount) else {
        print("🚫 Not enough balance to withdraw: (amount)")
        return
    }
    
    balance -= amount
    
    print("💰 Account balance: (balance)")
    
}

但是,假设authorizeTransaction方法非常耗时,而余额很明显是不满足扣款条件的,就是一种资源的浪费。所以我们可以:

swift 复制代码
func withdraw(_ amount: Int) async {
    
    print("🤓 Check balance for withdrawal: (amount)")
    guard canWithdraw(amount) else {
        print("🚫 Not enough balance to withdraw: (amount)")
        return
    }
    
    guard await authorizeTransaction() else {
        return
    }
    print("✅ Transaction authorized: (amount)")
    
    // Check balance again after the authorization process
    guard canWithdraw(amount) else {
        print("⛔️ Not enough balance to withdraw: (amount) (authorized)")
        return
    }
​
    balance -= amount
    
    print("💰 Account balance: (balance)")
    
}

虽然多做了一次canWithdraw,但是可以提升效率,且避免了重入的问题。

相关推荐
菜的不敢吱声21 小时前
swift学习第4天
服务器·学习·swift
菜的不敢吱声2 天前
swift学习第2,3天
python·学习·swift
大熊猫侯佩3 天前
拒绝“假死”:为何上滑关闭是测试大忌?揭秘 iOS 真实 OOM 触发指南
app·swift·apple
大熊猫侯佩3 天前
Swift 6.2 列传(第十六篇):阿朱的“易容术”与阿紫的“毒药测试”
swift·编程语言·apple
麦兜*3 天前
【Swift】苹果App开发全流程解析:从Xcode配置到App Store上架避坑指南
xcode·swift
JQShan4 天前
Core Data 简化开发:NSPersistentContainer 从原理到实战
swift
东坡肘子4 天前
2026:当 AI 隐入工作流,你准备好了吗? -- 肘子的 Swift 周报 #117
人工智能·swiftui·swift
菜的不敢吱声4 天前
swift学习第一天
开发语言·学习·swift
大熊猫侯佩5 天前
2026 码农漫游:AI 辅助 Swift 代码修复指南
swift·编程语言·apple
大熊猫侯佩5 天前
Swift 6.2 列传(第十五篇):王语嫣的《万剑归宗》与 InlineArray
swift·编程语言·apple