简而言之,不,属性访问器不是原子的。请参阅 WWDC 2016 视频 Concurrent Programming With GCD in Swift 3,其中讨论了该语言中缺乏原子/同步本机。 (这是一个 GCD 演讲,所以当他们随后深入研究同步方法时,他们专注于 GCD 方法,但任何同步方法都可以。)Apple 在自己的代码中使用了各种不同的同步方法。例如。在ThreadSafeArrayStore 他们使用they use NSLock)。
如果与锁同步,我可能会建议如下扩展:
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
Apple 在他们自己的代码中使用了这种模式,尽管他们碰巧称它为withLock 而不是synchronized。但是模式是一样的。
那么你可以这样做:
public class B {
private var lock = NSLock()
private var a: A? // make this private to prevent unsynchronized direct access to this property
public func setA() {
lock.synchronized {
a = A(0)
}
}
public func hasA() -> Bool {
lock.synchronized {
a != nil
}
}
public func resetA() {
lock.synchronized {
guard a != nil else { return }
a = A(1)
}
}
}
或许
public class B {
private var lock = NSLock()
private var _a: A?
public var a: A? {
get { lock.synchronized { _a } }
set { lock.synchronized { _a = newValue } }
}
public var hasA: Bool {
lock.synchronized { _a != nil }
}
public func resetA() {
lock.synchronized {
guard _a != nil else { return }
_a = A(1)
}
}
}
我承认公开hasA 时有些不安,因为它实际上会邀请应用程序开发人员这样写:
if !b.hasA {
b.a = ...
}
这在防止同时访问内存方面很好,但是如果两个线程同时执行它会引入逻辑竞争,其中两个线程都恰好通过了!hasA 测试,并且它们都替换了值,最后一个获胜。
相反,我可能会为我们编写一个方法来执行此操作:
public class B {
private var lock = NSLock() // replacing os_unfair_lock_s()
private var _a: A? = nil // fixed, thanks to Rob
var a: A? {
get { lock.synchronized { _a } }
set { lock.synchronized { _a = newValue } }
}
public func withA(block: (inout A?) throws -> T) rethrows -> T {
try lock.synchronized {
try block(&_a)
}
}
}
这样你就可以做到:
b.withA { a in
if a == nil {
a = ...
}
}
这是线程安全的,因为我们让调用者在一个同步步骤中包装所有逻辑任务(检查a 是否为nil,如果是,则a 的初始化)。这是一个很好的通用解决方案。并且它可以防止逻辑竞争。
现在上面的例子太抽象了,很难理解。因此,让我们考虑一个实际示例,Apple 的 ThreadSafeArrayStore 的变体:
public class ThreadSafeArrayStore<Value> {
private var underlying: [Value]
private let lock = NSLock()
public init(_ seed: [Value] = []) {
underlying = seed
}
public subscript(index: Int) -> Value {
get { lock.synchronized { underlying[index] } }
set { lock.synchronized { underlying[index] = newValue } }
}
public func get() -> [Value] {
lock.synchronized {
underlying
}
}
public func clear() {
lock.synchronized {
underlying = []
}
}
public func append(_ item: Value) {
lock.synchronized {
underlying.append(item)
}
}
public var count: Int {
lock.synchronized {
underlying.count
}
}
public var isEmpty: Bool {
lock.synchronized {
underlying.isEmpty
}
}
public func map<NewValue>(_ transform: (Value) throws -> NewValue) rethrows -> [NewValue] {
try lock.synchronized {
try underlying.map(transform)
}
}
public func compactMap<NewValue>(_ transform: (Value) throws -> NewValue?) rethrows -> [NewValue] {
try lock.synchronized {
try underlying.compactMap(transform)
}
}
}
这里有一个同步数组,我们在其中定义了一个接口,以线程安全的方式与底层数组进行交互。
或者,如果您想要一个更简单的示例,请考虑使用线程安全对象来跟踪最高的项目是什么。我们不会有 hasValue 布尔值,而是将其合并到同步的 updateIfTaller 方法中:
public class Tallest {
private var _height: Float?
private let lock = NSLock()
var height: Float? {
lock.synchronized { _height }
}
func updateIfTaller(_ candidate: Float) {
lock.synchronized {
guard let tallest = _height else {
_height = candidate
return
}
if candidate > tallest {
_height = candidate
}
}
}
}
仅举几个例子。希望它能说明这个想法。