Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 避免死锁与竞争的最佳实践

避免死锁与竞争的最佳实践

Swift的并发模型通过async/await和Actor简化了多线程编程,但数据竞争(Data Race)和死锁(Deadlock)仍是潜在风险。前两节介绍了数据竞争的识别和Thread Sanitizer的使用,本节将总结避免这些问题的最佳实践,从设计到实现提供具体指导。通过这些策略,你将能编写安全、高效的并发代码,防范常见的线程安全隐患。

数据竞争与死锁的回顾

  • 数据竞争:多线程无同步访问共享数据,导致结果不可预测。
  • 死锁:多个线程互相等待对方释放资源,陷入永久阻塞。

示例(死锁):

actor A {
    func callB(_ b: B) async { await b.callA(self) }
}

actor B {
    func callA(_ a: A) async { await a.callB(self) }
}

Task {
    let a = A()
    let b = B()
    async let task1 = a.callB(b)
    async let task2 = b.callA(a)
    await (task1, task2) // 可能死锁
}
  • 问题:A等待B,B等待A,循环依赖导致阻塞。

最佳实践

以下是避免数据竞争和死锁的实用建议:

1. 使用Actor隔离共享状态

策略:将共享数据封装在Actor中,确保串行访问。 实现:

actor Counter {
    private var count = 0
    func increment() { count += 1 }
    func getCount() -> Int { count }
}

Task {
    let counter = Counter()
    await withTaskGroup(of: Void.self) { group in
        for _ in 1...100 {
            group.addTask { await counter.increment() }
        }
    }
    print(await counter.getCount()) // 始终为100
}
  • 效果:Actor自动防止竞争,无需锁。

2. 避免直接访问共享变量

策略:禁止多线程直接操作类或结构体的实例变量。 实现:

// 错误:无保护
class UnsafeData {
    var value = 0
}

// 正确:用Actor
actor SafeData {
    private var value = 0
    func setValue(_ newValue: Int) { value = newValue }
}
  • 效果:强制通过Actor接口访问,消除竞争风险。

3. 谨慎处理Actor间的循环依赖

策略:避免Actor互相调用形成闭环,或使用非阻塞设计。 实现(修复死锁):

actor ResourceA {
    private var data = 0
    func process(_ b: ResourceB) async -> Int {
        let bValue = await b.getData() // 不调用B的process
        data += bValue
        return data
    }
}

actor ResourceB {
    private var data = 10
    func getData() -> Int { data }
}

Task {
    let a = ResourceA()
    let b = ResourceB()
    let result = await a.process(b)
    print("结果:\(result)") // 输出:10
}
  • 效果:打破循环,避免死锁。

4. 使用结构化并发

策略:优先使用Task Group管理并行任务,减少非结构化复杂性。 实现:

func fetchAllData() async -> [String] {
    await withTaskGroup(of: String.self) { group in
        for i in 1...3 {
            group.addTask { await fetchData(i) }
        }
        return await group.collectAll()
    }
}

func fetchData(_ id: Int) async -> String {
    try? await Task.sleep(nanoseconds: 500_000_000)
    return "数据\(id)"
}
  • 效果:任务关系清晰,自动清理,避免遗留竞争。

5. 检查和响应取消

策略:在长任务中定期检查Task.isCancelled,防止资源浪费。 实现:

Task {
    let task = Task {
        for i in 1...10 {
            try await Task.sleep(nanoseconds: 500_000_000)
            try Task.checkCancellation()
            print("进度:\(i)")
        }
    }
    try? await Task.sleep(nanoseconds: 2_000_000_000)
    task.cancel()
}
  • 效果:及时退出,减少死锁或竞争的潜在条件。

6. 最小化锁的使用

策略:若必须使用锁(如遗留代码),确保加锁范围最小化。 实现:

class LockedCounter {
    private var count = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        count += 1
        lock.unlock()
    }
}
  • 效果:减少锁持有时间,避免死锁。

7. 验证线程安全

策略:使用断言或日志确认线程上下文。 实现:

@MainActor
func updateUI(_ text: String) {
    assert(Thread.isMainThread)
    label.text = text
}
  • 效果:运行时验证,避免意外线程访问。

实战案例:库存管理优化

结合第十章的库存案例,应用最佳实践:

actor Inventory {
    private var stock: [String: Int] = [:]
    
    func add(id: String, quantity: Int) {
        stock[id, default: 0] += quantity
    }
    
    func remove(id: String, quantity: Int) throws -> Int {
        guard let current = stock[id], current >= quantity else {
            throw InventoryError.insufficientStock
        }
        stock[id] = current - quantity
        return current - quantity
    }
    
    func getStock() -> [String: Int] {
        stock
    }
}

enum InventoryError: Error { case insufficientStock }

@MainActor
class StockManager: UIViewController {
    private let inventory = Inventory()
    
    func processOrders() async throws {
        try await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask { try await self.inventory.remove(id: "book", quantity: 5) }
            group.addTask { await self.inventory.add(id: "pen", quantity: 10) }
            try await group.waitForAll()
        }
        let stock = await inventory.getStock()
        print("库存:\(stock)")
    }
}

Task {
    let manager = StockManager()
    try await manager.processOrders()
}
  • 实践应用:
    • Actor隔离库存状态。
    • Task Group结构化并行。
    • 错误集中处理。

验证与测试

  • 运行TSan:确保无竞争警告。
  • 压力测试:增加任务数量,验证一致性。
  • 日志检查:记录操作顺序,确认无死锁。

小结

避免数据竞争和死锁需要从设计到实现的全方位考虑。本节通过最佳实践,如使用Actor、结构化并发和取消检查,提供了防范并发问题的策略。结合库存案例,这些方法展示了如何构建健壮的异步代码。本章回顾了竞争与调试的全流程,下一章将探讨与遗留代码的集成,进一步扩展你的并发应用能力。


内容说明

  • 结构:从回顾到最佳实践,再到案例和总结。
  • 代码:包含死锁修复和库存优化示例,突出实用性。
  • 语气:指导性且总结性,适合技术书籍收尾章节。
  • 衔接:承接前两节(竞争识别和TSan),预告后续(遗留代码)。
Last Updated:: 3/5/25, 12:49 PM