在Swift中,类和结构的成员将被编译器生成的函数取消初始化(销毁)。
当没有强引用时,对象将被自动销毁。在最后一个强引用不再存在的任何线程上,该对象将在该线程上被销毁。这将破坏其所有结构成员,并可能销毁其成员对象,如果这也是最后一个强引用。
类型struct的值将在其范围停止退出后自动销毁。也就是说,无论发生什么线程,这个线程都会执行struct值的取消初始化。
我想知道这种取消初始化是否已经受到数据竞争的保护。我的第一个猜测是,它不是。但是当它不存在时,当有潜在的数据竞争时,当编译器 AND 生成这些成员变量的去初始化时,如何会插入正确的同步原语吗?
我将用一个例子说明这个问题:
public class Foo {
private let _syncQueue = DispatchQueue(label: "sync-queue", attributes: .serial)
private var _array: [String] = []
init() {
}
deinit {
}
func add(_ s: String) {
_syncQueue.async {
self._array.append(s)
}
}
}
这只是一个类,其实例可以在任何线程中使用,并且调用add(_:)
方法是线程安全的。因此,在任何情况下,使用类型为Foo
的对象都应该是线程安全的。实际上是吗?
好吧,如果取消初始化不同步,那就不是!
问题意识到当一个线程中存在最后一个强引用时,该线程与使用同步原语的先前访问的线程不同(例如,dispatch lib)和iff此线程发生通过其他一些不相关的操作与其他线程不同步。
我能够实际生成此数据竞争,然后在deinit
Foo
方法中生成。实际上并不容易找到发生这种情况的某些使用场景,更糟糕的是,它们总是在同一场景中发生(取决于GCD产生的当前线程)。但最终它发生了,并且在Xcode 8 beta中新的Thread Sanitizer的帮助下,我实际上发现了这些。
但是我仍然想知道,在目前的Swift语言中,这是否被视为“表现得如预期”或者这是否真的是一个错误。
如果Swift使用一些与任何线程同步的全局fence并且在取消初始化值之前使用,那么后者就是这种情况,但对于某些bug而言,在某些情况下它会被意外地使用。