Swift 5 中的引用赋值是原子的吗?
Posted
技术标签:
【中文标题】Swift 5 中的引用赋值是原子的吗?【英文标题】:is reference assignment atomic in Swift 5? 【发布时间】:2021-01-21 15:30:44 【问题描述】:在这种情况下我需要某种显式同步吗?
class A
let val: Int;
init(_ newVal: Int)
val = newVal
public class B
var a: A? = nil
public func setA() a = A(0)
public func hasA() -> Bool return a != nil
B类中还有另外一种方法:
public func resetA()
guard hasA() else return
a = A(1)
setA()
和 resetA()
可以从任何线程以任何顺序调用。
我了解可能存在竞争条件,即如果一个线程同时调用setA()
和另一个线程调用resetA()
,则结果不确定:val
将是0
或1
,但我不在乎:无论如何,hasA()
会返回 true,不是吗?
如果 A 是 struct 而不是 class,答案会改变吗?
【问题讨论】:
实际上用给定的代码——顺便说一下a
必须是可选的——你可以用lazy var a = A()
替换B
中的整个代码,这会懒惰地创建实例(一次),跨度>
@vadian 我相信我不能在这里使用lazy
:我想知道setA()
是否被显式调用。
@vadian lazy
不是线程安全的/也不是原子的。
var a: A = nil
这会给你编译错误,你的意思可能是var a: A? = nil
我也同意 Vadian 的观点,即使用惰性将确保那里只存在一个 A 的实例,因为它的原子性
“如果 A 是结构而不是类,答案会改变吗?” ... 没有。仍然不是原子的。现在,如果 (1) A
和 B
都是结构体,并且 (2) 你确保每个线程都有自己的副本,在任何地方都使用值语义,那么,是的,这确实解决了问题。
【参考方案1】:
简而言之,不,属性访问器不是原子的。请参阅 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
仅举几个例子。希望它能说明这个想法。
【讨论】:
非常感谢!这些例子肯定会在更广泛的背景下有所帮助。我的用例不同。 好的,现在我开始讨论另一个原子性问题:***.com/questions/66224497/…以上是关于Swift 5 中的引用赋值是原子的吗?的主要内容,如果未能解决你的问题,请参考以下文章