Swift 6中如何实现并发安全的静态属性操作?
我有一个基类A及其子类SubA_x,基类A包含静态属性serialNumber,该属性仅在子类初始化时由A修改,用于为每个子类实例设置唯一的name属性。相关代码如下:
class A { static var serialNumber = 0 let name: String init( /* some parameters */ ) { A.serialNumber += 1 self.name = "\(A.serialNumber)" } } final class SubA_1: A { init( /* some parameters */ ) { super.init( /* some parameters */ ) } }
在Swift 6严格并发检查下,修改serialNumber的代码行出现错误提示:
Static property 'serialNumber' is not concurrency-safe because it is
non-isolated global shared mutable state
我清楚子类可能在任意线程修改serialNumber,存在数据竞争风险。但如何以Swift并发框架原生方式实现该功能的并发安全?
我尝试用Actor管理serialNumber:
actor SerialNumberManager { private var serialNumber = 0 func getNextSerialNumber() -> Int { serialNumber += 1 return serialNumber } }
但无法在非异步的init中调用getNextSerialNumber();若将init改为异步,就只能在异步上下文初始化子类。我不想依赖GCD,希望用Swift原生并发框架解决问题。
方案1:使用Swift标准库Lock实现互斥访问
Swift 5.7+的Foundation框架提供了Lock类型,它是并发安全的互斥锁,可直接保护共享可变状态,完全符合Swift并发安全要求,且无需修改初始化方法的同步特性。修改基类A的代码如下:
import Foundation class A { private static let lock = Lock() private static var serialNumber = 0 let name: String init(/* some parameters */) { let nextNumber: Int Self.lock.withLock { Self.serialNumber += 1 nextNumber = Self.serialNumber } self.name = "\(nextNumber)" } } final class SubA_1: A { init(/* some parameters */) { super.init(/* some parameters */) } }
Lock.withLock会自动处理锁的加锁与解锁逻辑,确保同一时间只有一个线程能修改serialNumber,子类可正常在任意上下文初始化。
方案2:使用Actor结合同步等待(不推荐,可能阻塞线程)
如果一定要基于Actor实现,可以通过Task的同步等待方式在非异步init中获取序列号,但这种方式会阻塞当前线程,可能影响并发性能,仅适合对性能要求较低的场景:
actor SerialNumberManager { static let shared = SerialNumberManager() private var serialNumber = 0 func getNextSerialNumber() -> Int { serialNumber += 1 return serialNumber } } class A { let name: String init(/* some parameters */) { // 同步等待Actor返回序列号,会阻塞当前线程 let nextNumber = Task.detached(priority: .userInitiated) { await SerialNumberManager.shared.getNextSerialNumber() }.value self.name = "\(nextNumber)" } } final class SubA_1: A { init(/* some parameters */) { super.init(/* some parameters */) } }
注意:该方式会阻塞调用init的线程,若在主线程调用可能导致UI卡顿,因此更推荐方案1。
内容的提问来源于stack exchange,提问作者Reinhard Männer

