如何实现异步update函数的线程安全?并发调用引发数据异常
异步函数的线程安全问题及解决办法
问题场景
现有以下异步函数代码片段:
public func update(tables: [LocalTableItem]) async throws -> UpdateResult { let result = try await store.remove(tables: tables) print("** removing table \(tables.last!.name)") switch result { case .success: let insertResult = try await self.store.insert(tables: tables) print("** inserting table \(tables.last!.name)")
当通过多任务并发调用该函数时:
Task { _ = try await store.update(tables: [updatedTable]) }
日志输出显示:
** removing table icradle_loler_engineer.
** removing table icradle_loler_engineer.
** inserting table icradle_loler_engineer.
** inserting table icradle_loler_engineer.
此时remove与insert的调用顺序被打乱,导致数据库出现重复条目。尝试将类改为Actor后问题仍未解决,需实现该异步函数的线程安全。
可行解决方案
1. 修正Actor的使用方式
改成Actor后无效,通常是因为store对象未被正确隔离,或者update方法的调用路径没有经过Actor串行化。正确的实现方式:
- 将包含
update方法的类定义为Actor,同时确保内部依赖的store本身是线程安全的(比如store也是Actor,或其方法被Actor管控)。 - Actor会自动保证异步调用的串行执行,确保每个
update的“删除-插入”流程完整执行后,再处理下一个任务。示例代码:
actor StoreManager { private let store: YourStoreImplementation init(store: YourStoreImplementation) { self.store = store } func update(tables: [LocalTableItem]) async throws -> UpdateResult { let result = try await store.remove(tables: tables) print("** removing table \(tables.last!.name)") switch result { case .success: let insertResult = try await self.store.insert(tables: tables) print("** inserting table \(tables.last!.name)") // 补充返回逻辑,比如返回insertResult case .failure(let error): throw error } } }
调用时必须通过Actor实例发起,不能直接绕开Actor访问内部方法:
let manager = StoreManager(store: yourStoreInstance) Task { _ = try await manager.update(tables: [updatedTable]) }
2. 用锁同步原子操作
如果不想使用Actor,可以用NSLock或os_unfair_lock将“删除-插入”操作包裹为原子操作,确保同一时间只有一个线程执行该流程:
private let updateLock = NSLock() public func update(tables: [LocalTableItem]) async throws -> UpdateResult { updateLock.lock() defer { updateLock.unlock() } // 确保锁最终被释放 let result = try await store.remove(tables: tables) print("** removing table \(tables.last!.name)") switch result { case .success: let insertResult = try await self.store.insert(tables: tables) print("** inserting table \(tables.last!.name)") // 补充返回逻辑 case .failure(let error): throw error } }
注意:如果store的remove/insert方法本身耗时较长,这种方式会阻塞其他任务,影响并发性能。
3. 数据库事务保证原子性
最可靠的方式是在数据库层面开启事务,将“删除-插入”操作纳入同一个事务中。数据库会保证事务的原子性,要么全部执行成功,要么全部回滚,避免出现中间状态:
public func update(tables: [LocalTableItem]) async throws -> UpdateResult { try await store.beginTransaction() defer { // 若事务未提交,自动回滚 if store.isInTransaction { try? store.rollbackTransaction() } } let result = try await store.remove(tables: tables) print("** removing table \(tables.last!.name)") switch result { case .success: let insertResult = try await self.store.insert(tables: tables) print("** inserting table \(tables.last!.name)") try await store.commitTransaction() return insertResult // 或自定义的成功结果 case .failure(let error): throw error } }
不同数据库的事务API有所差异,需根据你使用的数据库框架调整具体实现。
内容的提问来源于stack exchange,提问作者user3010251
相关产品推荐
相关产品推荐

