示例:用Actor管理共享状态
Actor通过数据隔离和串行访问为共享状态提供了线程安全的保障,前两节介绍了其定义和机制。本节将通过一个具体的实战案例——实现一个多线程访问的库存管理系统——展示如何使用Actor管理共享状态。我们将设计一个库存Actor,处理并发添加和移除操作,并确保数据一致性,最终集成到UI中。通过这个案例,你将掌握Actor在实际开发中的应用技巧。
案例背景
假设我们要开发一个电商应用的库存管理模块,要求:
- 多线程同时添加或移除商品库存(如用户下单、退货)。
- 确保库存数量不会出现负值或数据竞争。
- 在主线程更新UI显示库存状态。
- 支持异步操作和错误处理。
以下是逐步实现的过程。
实现步骤
1. 定义库存模型和错误类型
首先定义库存项和可能的错误:
struct InventoryItem {
let id: String
var quantity: Int
}
enum InventoryError: Error {
case insufficientStock
case itemNotFound
}
InventoryItem:表示商品及其库存量。InventoryError:处理库存不足或未找到的情况。
2. 创建InventoryActor
设计一个Actor管理库存状态:
actor InventoryActor {
private var stock: [String: InventoryItem] = [:]
func addItem(id: String, quantity: Int) {
if let existing = stock[id] {
stock[id] = InventoryItem(id: id, quantity: existing.quantity + quantity)
} else {
stock[id] = InventoryItem(id: id, quantity: quantity)
}
}
func removeItem(id: String, quantity: Int) throws -> Int {
guard let item = stock[id] else {
throw InventoryError.itemNotFound
}
guard item.quantity >= quantity else {
throw InventoryError.insufficientStock
}
let newQuantity = item.quantity - quantity
stock[id] = InventoryItem(id: id, quantity: newQuantity)
return newQuantity
}
func getStock(id: String) -> Int? {
stock[id]?.quantity
}
func getAllStock() -> [InventoryItem] {
Array(stock.values)
}
}
- 私有状态:
stock字典存储库存。 - 方法:
addItem:添加库存。removeItem:移除库存,检查数量并抛出错误。getStock和getAllStock:查询库存。
3. 测试多线程访问
模拟多线程操作验证线程安全:
Task {
let inventory = InventoryActor()
// 初始化库存
await inventory.addItem(id: "book", quantity: 100)
// 并行操作
await withTaskGroup(of: Void.self) { group in
// 10个任务添加库存
for _ in 1...10 {
group.addTask {
await inventory.addItem(id: "book", quantity: 5)
}
}
// 10个任务移除库存
for _ in 1...10 {
group.addTask {
do {
let _ = try await inventory.removeItem(id: "book", quantity: 3)
} catch {
print("移除失败:\(error)")
}
}
}
}
let finalStock = await inventory.getStock(id: "book")
print("最终库存:\(finalStock ?? 0)") // 预期:120 (100 + 10*5 - 10*3)
}
- 并行任务:20个任务同时操作
book库存。 - 结果:
Actor确保操作串行,最终库存准确。
4. 集成到视图控制器
将库存管理整合到UI,支持实时更新:
@MainActor
class StockViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var statusLabel: UILabel!
private let inventory = InventoryActor()
private var stockItems: [InventoryItem] = []
override func viewDidLoad() {
super.viewDidLoad()
initializeStock()
simulateOperations()
}
private func initializeStock() {
Task {
await inventory.addItem(id: "book", quantity: 50)
await inventory.addItem(id: "pen", quantity: 30)
await updateUI()
}
}
private func simulateOperations() {
Task {
await withTaskGroup(of: Void.self) { group in
group.addTask {
try? await self.inventory.removeItem(id: "book", quantity: 10)
await self.updateUI()
}
group.addTask {
await self.inventory.addItem(id: "pen", quantity: 5)
await self.updateUI()
}
}
}
}
private func updateUI() async {
stockItems = await inventory.getAllStock()
tableView.reloadData()
statusLabel.text = "库存更新完成"
}
@IBAction func reduceStock(_ sender: UIButton) {
Task {
do {
let remaining = try await inventory.removeItem(id: "book", quantity: 5)
statusLabel.text = "移除成功,剩余:\(remaining)"
await updateUI()
} catch InventoryError.insufficientStock {
statusLabel.text = "库存不足"
} catch {
statusLabel.text = "错误:\(error)"
}
}
}
}
// UITableViewDataSource
extension StockViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
stockItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StockCell", for: indexPath)
let item = stockItems[indexPath.row]
cell.textLabel?.text = item.id
cell.detailTextLabel?.text = "数量:\(item.quantity)"
return cell
}
}
@MainActor:确保UI操作在主线程。- 并行操作:模拟多线程添加和移除。
- 错误处理:用户点击减少库存时显示状态。
运行结果
- 初始状态:
book: 50, pen: 30。 - 模拟操作后:
book: 40, pen: 35(移除10,添加5)。 - 用户操作:点击减少
book库存,成功则更新,失败则提示“库存不足”。
分析与改进
优势
- 线程安全:多线程操作库存无竞争,数量始终正确。
- 简单性:无需锁或队列,
Actor自动隔离。 - UI集成:与
async/await和MainActor无缝协作。
可改进之处
- 进度反馈:显示正在处理的请求数。
- 批量操作:支持一次性添加/移除多个项目:
func batchUpdate(updates: [(id: String, quantity: Int)]) async { for update in updates { await addItem(id: update.id, quantity: update.quantity) } } - 日志记录:用另一个
Actor记录操作历史。
测试建议
- 并发压力:增加更多并行任务,验证隔离性。
- 边界情况:尝试移除超过库存的数量,检查错误处理。
- 取消支持:添加任务取消逻辑。
小结
本节通过库存管理案例展示了Actor在共享状态管理中的强大能力。从设计到UI集成,Actor确保了线程安全和数据一致性。这一实践整合了数据隔离和异步编程的知识,体现了Actor的实际价值。掌握此案例,你将能自信应对并发状态管理。下一章将探讨MainActor与UI线程的结合,进一步完善你的并发技能。
内容说明
- 结构:从背景到实现,再到分析和总结。
- 代码:完整案例覆盖模型、Actor和UI,突出实用性。
- 语气:实践性且深入,适合技术书籍核心章节。
- 衔接:承接前两节(
Actor基础和隔离),预告后续(MainActor)。
