Apple说:“如果使用值类型,则可以安全地跨线程传递值的副本,而无需同步。”但是我最近发现与苹果指南的并发崩溃有所不同。
我看到https://developer.apple.com/swift/blog/?id=10和Apple的指南说“值类型在多线程中是安全的”,所以我认为“值类型是原子的!”但是最近我在下面的代码中看到了并发崩溃。
class ClassB: NSObject {
func readSomeValue() {
print(classA.someValue)
}
let classA = ClassA()
}
class ClassA: NSObject {
private(set) var someValue: StructA? {
didSet{
if oldValue != self.someValue { self.someFunction(self.someValue) }
}
}
private func modifySomeValue(_ newValue: StructA) {
self.someValue = newValue
}
}
struct StructA {
var a: Double
var b: String?
}
在线程1中执行readSomeValue()并在线程2中执行ModifySomeValue()时,发生并发崩溃。 为什么会发生并发崩溃?值类型在多线程中不安全吗?
答案 0 :(得分:2)
一些观察结果:
重要的是,您可以安全地跨线程传递值的副本,而无需同步。
这里的有效词是“副本”。
但是在您的示例中,您没有将值类型对象的副本传递给不同的线程。您在线程之间共享引用类型对象的单个实例class
。当然,您的引用类型具有值类型属性,但这不会改变您在线程之间共享引用类型对象实例的单个实例这一事实。您必须手动将与该对象及其属性的交互同步,以享受线程安全性。
有一个论点是,许多讨论误导读者以为Swift值类型总是享受复制(或写时复制)语义,因此总是享受这种线程安全功能。但是您必须要小心,因为有几个示例没有提供复制语义。您在引用类型对象中具有value-type属性的示例就是一个示例。
另一个例子是您无法使用闭包“capture lists”。例如,以下内容不是线程安全的,因为它在多个线程中使用相同的值类型实例:
var object = StructA(a: 42, b: "foo")
DispatchQueue.global().async {
print(object)
}
object.b = "bar"
但是,通过添加捕获列表,全局队列将拥有其自己的对象副本,从而在线程之间恢复此线程安全交互,因为每个线程都具有自己的对象副本:
var object = StructA(a: 42, b: "foo")
DispatchQueue.global().async { [object] in
print(object)
}
object.b = "bar"
是的,如果您(a)使用值类型,则可以编写线程安全代码。 (b)传递这些值类型的副本。但这与原子性无关。最重要的是,Swift变量不是原子变量。