数据竞争问题
了解并发变更为何不安全。
数据竞争问题 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
什么是数据竞争
当两个或更多线程同时访问同一内存位置,且至少有一次访问是写入,同时它们之间没有同步机制时,就会发生数据竞争。
结果是未定义行为:值被破坏、发生崩溃,或者只在高负载下出现错误。
共享可变状态
数据竞争的根本原因是共享可变状态。如果多个任务都可以读取和写入同一个变量,执行顺序就会变得不可预测。
下面的计数器可能会丢失增量,因为count += 1是读取—修改—写入操作,而不是原子操作。
final class Counter {
var count = 0
func increment() {
count += 1 // read, add, write: not atomic
}
}为什么会丢失增量
count += 1语句会编译成三个步骤:读取值、加一、再写回。
如果两个线程同时读取5,它们都会写入6,其中一次增量就会消失。
// Thread A loads 5
// Thread B loads 5
// Thread A stores 6
// Thread B stores 6 <-- lost update较大值的撕裂
除了更新丢失之外,对多字值的并发写入(例如结构体,或某些平台上的 64 位值)还可能发生撕裂:读取者看到的是一次写入的一半和另一次写入的一半。
struct Point { var x: Double; var y: Double }
var p = Point(x: 0, y: 0)
// Concurrent writes may leave x from one write and y from another旧式修复方案:锁
在 Swift 并发出现之前,经典的补救方案是使用锁(互斥锁)。同一时间只有一个线程持有锁,从而将访问串行化。
锁确实有效,但很容易误用:忘记解锁、死锁以及优先级反转都可能发生。
import Foundation
final class SafeCounter {
private let lock = NSLock()
private var count = 0
func increment() {
lock.lock()
defer { lock.unlock() }
count += 1
}
}串行调度队列
另一种经典方案是使用串行调度队列。所有修改都集中到一个队列中,因此它们永远不会重叠执行。
import Foundation
final class QueueCounter {
private let queue = DispatchQueue(label: "counter")
private var count = 0
func increment() {
queue.async { self.count += 1 }
}
}为什么手动同步很脆弱
锁和队列依赖于规范操作。编译器不会检查每次访问是否都受到保护。
只要有一次未受保护的读取遗漏,竞争就会再次出现。编译时没有任何保证。
// Nothing stops a careless reader from doing this:
// let value = counter.count // unsynchronized read = raceSwift 并发改变了局面
Swift 并发将数据竞争安全从一种约定变成了语言特性。
三个工具会协同工作:使用actor保护可变状态,使用Sendable表示可安全共享的类型,再由编译器强制执行这两点。
actor Counter {
private var count = 0
func increment() { count += 1 }
}参与者串行化访问
参与者保证同一时间只有一个任务运行其修改代码。运行时会自动将访问串行化。
您无需编写锁;参与者模型会提供同步机制。
actor BankAccount {
private(set) var balance = 0
func deposit(_ amount: Int) { balance += amount }
}编译时强制执行
如果不通过参与者,编译器会拒绝让您访问参与者隔离的状态。跨参与者调用会变为异步调用(await)。
这样就能将运行时竞争转化为编译时错误。
let account = BankAccount()
// Must await: balance is actor-isolated
// let b = await account.balanceSendable 限定跨线程传递的内容
Sendable协议用于标记可以安全跨越并发边界传递的类型。
编译器会阻止将非Sendable的可变状态发送到另一个任务,从类型层面消除竞争。
struct Money: Sendable {
let amount: Int
let currency: String
}快速检查:数据竞争
请检验您对数据竞争成因的理解。
回顾:数据竞争问题
数据竞争源于共享可变状态在没有同步机制的情况下被并发访问,从而导致更新丢失、撕裂和未定义行为。
旧式修复方案(锁、串行队列)虽然有效,但缺少检查且很脆弱。Swift 并发用强制执行取代约定:actor隔离状态,Sendable限定可以跨越边界的内容,编译器则验证安全性。本课程的其余部分将深入探讨这些工具。
常见问题解答
「数据竞争问题」课时是免费的吗?
是的 — 「数据竞争问题」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「数据竞争问题」这节课中我会学到什么?
了解并发变更为何不安全。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「数据竞争问题」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。