Swift Actor转换后仍存数据竞争?求原因分析与优化方案
问题描述
把Swift应用里的问题类改成actor后,原来的崩溃没了,但Thread Sanitizer还是报数据竞争。核心代码如下:
actor SampleActor { private var things = Set<Int>() func addThing(_ newThing: Int, seconds: Double) { things.insert(newThing) Task { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) self.things.remove(newThing) } } } // 触发竞争的代码 let sample = SampleActor() for n in 0 ..< 1000 { Task { await sample.addThing(n, seconds: Double.random(in: 0...1.0)) } }
需求是给SampleActor里的集合添加对象,过一段时间自动移除。
问题原因
你忽略了actor内部创建的Task默认不会继承当前actor的隔离上下文:
addThing方法是在actor的串行队列里执行的,但里面新建的Task会跑到全局并发队列上执行。- 等
Task.sleep挂起恢复后,代码直接访问self.things,此时没有获取actor的执行权限,相当于绕开了actor的串行保护,和其他正在执行addThing的方法同时操作things集合,从而触发数据竞争。
修复方案
两种靠谱的实现方式:
方式一:封装移除操作到actor方法
把移除逻辑做成actor的私有方法,通过await调用,确保操作在actor的串行队列里执行:
actor SampleActor { private var things = Set<Int>() func addThing(_ newThing: Int, seconds: Double) { things.insert(newThing) // 用detached避免强引用,防止内存泄漏 Task.detached { [weak self] in try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) await self?.removeThing(newThing) } } private func removeThing(_ thing: Int) { things.remove(thing) } }
方式二:在Task中显式切换到actor上下文
如果不想额外加方法,也可以在Task里先await self切换到actor的隔离上下文,再访问属性:
actor SampleActor { private var things = Set<Int>() func addThing(_ newThing: Int, seconds: Double) { things.insert(newThing) Task { [weak self] in try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) guard let self = self else { return } await self.things.remove(newThing) } } }
更优实现建议
- 所有对actor内部状态
things的修改,都尽量封装成actor的方法,不要直接访问属性,确保所有状态操作都在actor的串行队列里执行,从根源上避免竞争。 - 如果要处理大量延迟任务,可以考虑用更高效的任务管理方式(比如统一维护定时器列表),但当前需求用上面的修复就足够安全。
内容的提问来源于stack exchange,提问作者RL2000
相关产品推荐
相关产品推荐

