Swift 并发避坑指南:自己动手实现“原子”属性与集合

原文:Atomic properties and collections in Swift

为什么需要"原子"操作?

Swift 没有现成的 atomic 关键字。当多个线程/任务同时读写同一属性或集合时,会出现:

  • 读到中间状态(数组越界、字典重复 key)
  • 丢失更新(值类型复制-修改-写回)

使用Swift 实现Atomic有多种方案,以下从简单到高级介绍iOS17及以下系统的可行方案;最后补充 iOS 18 原生 Synchronization 框架的替代思路。

方案 1:属性包装器 + GCD(入门级)

swift 复制代码
@propertyWrapper
struct Atomic<Value> {
    private let queue = DispatchQueue(label: "atomic.queue") // 串行队列
    private var storage: Value
    
    init(wrappedValue: Value) { storage = wrappedValue }
    
    var wrappedValue: Value {
        // 串行队列同步执行,类似锁机制,读写都需要排队
        get { queue.sync { storage } }
        set { queue.sync { storage = newValue } }   // 注意:非 barrier
    }
}

使用

swift 复制代码
class Client {
    @Atomic var counter = 0
}

踩坑:复合运算"非原子"

swift 复制代码
let client = Client()
client.counter += 1   // 读 + 改 + 写 三步,线程不安全!

方案 2:暴露 modify 闭包(中级)

swift 复制代码
@propertyWrapper
class Atomic<Value> {          // 改为 class,避免值拷贝
    private let queue = DispatchQueue(label: "atomic.queue") // 串行队列
    private var storage: Value
    
    var projectedValue: Atomic<Value> { self }
    
    init(wrappedValue: Value) { storage = wrappedValue }
    
    var wrappedValue: Value {
        // 串行队列同步执行,类似锁机制,读写都需要排队
        // 这里对应整体取值和整体赋值
        get { queue.sync { storage } }
        set { queue.sync { storage = newValue } }
    }
    
    // 原子"读-改-写"
    // 这里对应复合运算
    func modify(_ block: (inout Value) -> Void) {
        queue.sync { block(&storage) }   // 整个 block 在串行队列里执行
    }
}

使用

swift 复制代码
client.$counter.modify { $0 += 1 }        // ✅ 线程安全
client.$counter.modify { $0.append(42) }  // 同样适用于数组

方案 3:独立 Atomic<T> 类(高级,API 更友好)

swift 复制代码
final class Atomic<Value> {
    private var value: Value
    private let queue = DispatchQueue(label: "atomic.queue", attributes: .concurrent) // 并发队列
    
    init(_ value: Value) { self.value = value }
    
    // 1. 只读快照
    func read<T>(_ keyPath: KeyPath<Value, T>) -> T {
        queue.sync { value[keyPath: keyPath] }
    }
    
    // 2. 读并转换
    func read<T>(_ transform: (Value) throws -> T) rethrows -> T {
        try queue.sync { try transform(value) }
    }
    
    // 3. 整体替换
    func modify(_ newValue: Value) {
        queue.async(flags: .barrier) { self.value = newValue }
    }
    
    // 4. 原子读-改-写
    func modify(_ transform: (inout Value) -> Void) {
        queue.sync(flags: .barrier) { transform(&value) }
    }
}

使用

swift 复制代码
let num = Atomic(0)
num.modify { $0 += 1 }
print(num.read(\.self))   // 1

let arr = Atomic([1, 2, 3])
arr.modify { $0.append(4) }
print(arr.read { $0.contains(4) }) // true

并发队列 + barrier 保证"写"互斥,"读"并行,性能优于串行队列。

方案 4:专用原子集合(线程安全数组)

swift 复制代码
class AtomicArray<Element> {
    private var storage: [Element] = []
    private let queue = DispatchQueue(label: "atomic.array", attributes: .concurrent)
    
    subscript(index: Int) -> Element {
        get { queue.sync { storage[index] } }
        set { queue.async(flags: .barrier) { self.storage[index] = newValue } }
    }
    
    func append(_ new: Element) {
        queue.async(flags: .barrier) { self.storage.append(new) }
    }
    
    func removeAll() {
        queue.async(flags: .barrier) { self.storage.removeAll() }
    }
    
    func forEach(_ body: (Element) throws -> Void) rethrows {
        try queue.sync { try storage.forEach(body) }
    }
    
    func all() -> [Element] { queue.sync { storage } }
}

压测:10 线程并发 append

swift 复制代码
Task.detached {
    let arr = AtomicArray<Int>()
    DispatchQueue.concurrentPerform(iterations: 10_000) { @Sendable in  arr.append($0)}
    print(arr.all().count)   // 10_000 ✅
}✅

方案 5:原子字典(读多写少最优解)

swift 复制代码
class AtomicDictionary<Key: Hashable, Value> {
    private var dict: [Key: Value] = [:]
    private let queue = DispatchQueue(label: "atomic.dict", attributes: .concurrent)
    
    subscript(key: Key) -> Value? {
        get { queue.sync { dict[key] } }
        set { queue.async(flags: .barrier) { self.dict[key] = newValue } }
    }
    
    subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value {
        get {
            queue.sync { dict[key] ?? defaultValue() }
        }
        set {
            queue.async(flags: .barrier) { self.dict[key] = newValue }
        }
    }
}

方案 6 _read / _modify 原生读写访问器

⚠️ 下划线 API,非稳定,仅用于实验。

swift 复制代码
@propertyWrapper
struct Atomic<Value> {
    private var storage: Value
    private let lock = NSLock()
    
    var wrappedValue: Value {
        get { lock.lock(); defer { lock.unlock() }; return storage }
        
        _modify {              // 原生"产出"引用,零拷贝
            lock.lock()
            defer { lock.unlock() }
            yield &storage
        }
    }
}

优点:

  • 无队列调度,纯锁+直接内存引用,性能更高。

缺点:

  • 未来可能改名或移除;不建议上生产。

iOS 18+ 新选择:Apple Synchronization 框架

swift 复制代码
import Synchronization

let counter = Atomic<Int>(0)   // 系统级原子类型,无锁、CPU 指令级
counter.wrappingIncrement(by: 1)
  • 真正无锁(使用 std::atomic 底层)。
  • 支持泛型、Sendable、wait/notify 等高级语义。
  • 最低部署版本 iOS 18;若需向下兼容,仍需本文方案。

选型速查表

场景 推荐方案
只支持 iOS 18+ 系统 Synchronization
属性读写,iOS 17 及以下 方案 3 Atomic<Value>
专用集合(数组/字典) 方案 4 / 5 原子集合类
超高性能、实验 方案 6 _read/ _modify

结论

  • Swift 没有"官方原子"≠ 不能写出线程安全的代码。
  • GCD + barrier + 值类型拷贝 足以覆盖 99 % 业务需求。
  • 提前布局:当 iOS 18 普及后,迁移到 Synchronization 只需换一行 import。

记住口诀:

"读并行、写互斥,修改用 barrier,集合要包类。"

相关推荐
HarderCoder1 天前
惊!只是 `import Foundation`,`String.contains("")` 的返回值居然变了?
swift
HarderCoder1 天前
Swift 6.2 新武器:`weak let` —— 既弱引用又不可变的安全魔法
swift
HarderCoder1 天前
吃透 Swift 的 `@autoclosure`:把“表达式”变“闭包”的延迟利器
swift
HarderCoder1 天前
@Observable 遇上属性包装器:一键绕过‘计算属性’禁令的 Swift 5.9 实战技巧
swift
HarderCoder1 天前
如何绕过“Extensions must not contain stored properties”错误
swift
HarderCoder2 天前
彻底搞懂 Swift 的 Any、AnyObject、any
swift
HarderCoder2 天前
OptionSet vs Enum:Swift 中如何优雅地表达“多选”?
swift
HarderCoder2 天前
Swift 的 Optional.take():一次性消费值的神器
swift
MichaelIp2 天前
利用ms-swift微调和百炼平台微调大模型
人工智能·gpt·自然语言处理·prompt·aigc·swift·agi