You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift服务端项目:基于Actor实现原子缓存的问题与解决思路问询

基于Swift Actor实现原子性缓存的解决方案

你的思路方向是对的,但确实因为Actor的重入特性(你提到的reentrancy),异步操作会导致多个任务同时进入数据生成逻辑,进而重复计算。不过通过给Actor添加额外的状态标记,完全可以解决这个问题。

核心问题分析

你担心的异步操作破坏原子性,本质是Actor在执行await时会暂时放弃执行权,允许其他任务进入Actor的方法。如果没有状态标记,多个调用get()的任务会在缓存未命中且无生成中的情况下,都进入数据生成流程,导致重复计算和写入。

改进后的Actor实现

我们可以给CacheItem添加一个pendingTask状态,用来标记当前是否有正在进行的数据生成任务。第一个任务进入生成流程时,创建异步任务并保存引用;后续任务直接等待这个任务完成,就能避免重复工作:

actor CacheItem {
    private let key: String
    private let storage: any CacheStorage
    
    private var cachedValue: String?
    // 标记正在进行的生成任务,避免重复执行
    private var pendingTask: Task<String, Error>?
    
    init(key: String, backingStorage: any CacheStorage) {
        self.key = key
        self.storage = backingStorage
    }
    
    func get() async throws -> String {
        // 1. 优先返回本地缓存的值
        if let value = cachedValue {
            return value
        }
        
        // 2. 如果有正在进行的生成任务,直接等待其完成
        if let task = pendingTask {
            return try await task.value
        }
        
        // 3. 没有缓存也没有生成任务,开始生成
        let task = Task {
            defer {
                // 任务完成后清除标记,无论成功失败
                pendingTask = nil
            }
            
            // 先尝试从后端存储读取
            if let storedValue = try await storage.get(key: key) {
                cachedValue = storedValue
                return storedValue
            }
            
            // 生成新值(替换成你的实际异步生成逻辑)
            let newValue = try await generateValueForKey(key)
            
            // 写入后端存储和本地缓存
            try await storage.set(key: key, value: newValue)
            cachedValue = newValue
            
            return newValue
        }
        
        pendingTask = task
        return try await task.value
    }
    
    // 模拟数据生成的异步方法
    private func generateValueForKey(_ key: String) async throws -> String {
        // 可替换为数据库查询、API请求等异步操作
        try await Task.sleep(nanoseconds: 1_000_000_000)
        return "generated_value_for_\(key)"
    }
}

// 定义缓存存储协议,适配Redis等后端
protocol CacheStorage {
    func get(key: String) async throws -> String?
    func set(key: String, value: String) async throws
}

关键改进点

  • pendingTask状态标记:用Task实例跟踪正在进行的生成任务,后续请求直接复用任务结果,彻底避免重复计算。
  • defer清除标记:确保任务无论成功还是失败,都会清除pendingTask,避免后续请求陷入无限等待。
  • 本地缓存+后端存储结合:本地缓存(cachedValue)减少对后端存储的重复读取,提升整体性能。

关于Actor重入的说明

Actor的重入特性本身不是问题,只要我们通过状态标记明确当前的执行阶段,就能避免竞态条件。官方文档提到的“无await时安全更新”,是指同步代码块中修改状态不会出现中间状态被观测的情况;而通过pendingTask这种方式,我们把异步操作的状态也纳入了Actor的管理,依然能保证原子性。

替代方案:全局缓存Actor代理

如果觉得每个Key对应一个Actor开销太大,也可以用一个全局的CacheActor作为所有缓存键的互斥代理,通过字典管理每个Key的pendingTask:

actor CacheActor {
    private let storage: any CacheStorage
    private var cache: [String: String] = [:]
    private var pendingTasks: [String: Task<String, Error>] = [:]
    
    init(storage: any CacheStorage) {
        self.storage = storage
    }
    
    func get(key: String) async throws -> String {
        if let value = cache[key] {
            return value
        }
        
        if let task = pendingTasks[key] {
            return try await task.value
        }
        
        let task = Task { [key] in
            defer {
                pendingTasks[key] = nil
            }
            
            if let storedValue = try await storage.get(key: key) {
                cache[key] = storedValue
                return storedValue
            }
            
            let newValue = try await generateValueForKey(key)
            try await storage.set(key: key, value: newValue)
            cache[key] = newValue
            
            return newValue
        }
        
        pendingTasks[key] = task
        return try await task.value
    }
    
    private func generateValueForKey(_ key: String) async throws -> String {
        try await Task.sleep(nanoseconds: 1_000_000_000)
        return "generated_value_for_\(key)"
    }
}

这种方案更适合缓存键数量较多的场景,避免创建大量Actor实例。

内容的提问来源于stack exchange,提问作者Jerry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 11:21:02