ios - 如果我使用带屏障的 OS 原子函数写入/交换,在 64 位平台上读取 64 位原子值是否安全?
问题描述
问题是关于最新的 iOS 和 macOS。假设我在 Swift 中有以下原子 Int64 实现:
struct AtomicInt64 {
private var _value: Int64 = 0
init(_ value: Int64) {
set(value)
}
mutating func set(_ newValue: Int64) {
while !OSAtomicCompareAndSwap64Barrier(_value, newValue, &_value) { }
}
mutating func setIf(expectedValue: Int64, _ newValue: Int64) -> Bool {
return OSAtomicCompareAndSwap64Barrier(expectedValue, newValue, &_value)
}
var value: Int64 { _value }
}
注意value
访问者:它安全吗?
如果没有,我应该怎么做才能以原子方式获取值?
此外,同一类的 32 位版本是否安全?
编辑请注意,问题与语言无关。以上内容可以用任何生成 CPU 指令的语言编写。
编辑 2 OSAtomic 界面现在已弃用,但我认为任何替代品都或多或少具有相同的功能和相同的幕后行为。因此,能否安全读取 32 位和 64 位值的问题仍然存在。
编辑 3当心在 GitHub 和 SO 上流传的不正确的实现:读取值也应该以安全的方式进行(见下面 Rob 的回答)
解决方案
此OSAtomic
API 已弃用。文档没有提到它,你也没有看到来自 Swift 的警告,但是从 Objective-C 中使用你会收到弃用警告:
'OSAtomicCompareAndSwap64Barrier' 已弃用:首先在 iOS 10 中弃用 - 改用 atomic_compare_exchange_strong()
(如果在 macOS 上工作,它会警告您它在 macOS 10.12 中已被弃用。)
您问:
OSAtomic 接口现在已被弃用,但我认为任何替代品都或多或少具有相同的功能和相同的幕后行为。因此,能否安全读取 32 位和 64 位值的问题仍然存在。
建议的替换是stdatomic.h
. 它有一个atomic_load
方法,我会使用它而不是直接访问。
就个人而言,我建议您不要使用OSAtomic
. 在 Objective-C 中,您可以考虑使用stdatomic.h
,但在 Swift 中,我建议使用标准的通用同步机制之一,例如 GCD 串行队列、GCD 读写器模式或NSLock
基于方法。传统观点认为 GCD 比锁更快,但我最近的所有基准测试似乎表明现在情况正好相反。
所以我可能会建议使用锁:
struct Synchronized<Value> {
private var _value: Value
private var lock = NSLock()
init(_ value: Value) {
self._value = value
}
var value: Value {
get { lock.synchronized { _value } }
set { lock.synchronized { _value = newValue } }
}
mutating func synchronized<T>(block: (inout Value) throws -> T) rethrows -> T {
return try lock.synchronized {
try block(&_value)
}
}
}
通过这个小扩展(受 ApplewithCriticalSection
方法的启发)提供更简单的NSLock
交互:
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
然后,我可以声明一个同步整数:
var foo = Synchronized<Int>(0)
现在我可以像这样从多个线程中增加一百万次:
DispatchQueue.concurrentPerform(iterations: 1_000_000) { _ in
foo.synchronized { value in
value += 1
}
}
print(foo.value) // 1,000,000
请注意,虽然我为 提供了同步访问器方法value
,但这仅适用于简单的加载和存储。我在这里没有使用它,因为我们希望整个加载、增量和存储作为单个任务同步。所以我正在使用该synchronized
方法。考虑以下:
DispatchQueue.concurrentPerform(iterations: 1_000_000) { _ in
foo.value += 1
}
print(foo.value) // not 1,000,000 !!!
它看起来很合理,因为它使用了同步value
访问器。但它只是不起作用,因为同步逻辑处于错误的级别。我们确实需要将所有三个步骤同步在一起,而不是单独同步该值的加载、增量和存储。因此,我们将整体包装value += 1
在synchronized
闭包中,如前面的示例所示,并实现所需的行为。
顺便说一句,请参阅Use queue and semaphore for concurrency and property wrapper? 对于这种同步机制的一些其他实现,包括 GCD 串行队列、GCD 读写器、信号量等,以及一个单元测试,不仅对这些进行基准测试,而且还说明了简单的原子访问器方法不是线程安全的.
如果你真的想使用stdatomic.h
,你可以在 Objective-C 中实现它:
// Atomic.h
@import Foundation;
NS_ASSUME_NONNULL_BEGIN
@interface AtomicInt: NSObject
@property (nonatomic) int value;
- (void)add:(int)value;
@end
NS_ASSUME_NONNULL_END
和
// AtomicInt.m
#import "AtomicInt.h"
#import <stdatomic.h>
@interface AtomicInt()
{
atomic_int _value;
}
@end
@implementation AtomicInt
// getter
- (int)value {
return atomic_load(&_value);
}
// setter
- (void)setValue:(int)value {
atomic_store(&_value, value);
}
// add methods for whatever atomic operations you need
- (void)add:(int)value {
atomic_fetch_add(&_value, value);
}
@end
然后,在 Swift 中,您可以执行以下操作:
let object = AtomicInt()
object.value = 0
DispatchQueue.concurrentPerform(iterations: 1_000_000) { _ in
object.add(1)
}
print(object.value) // 1,000,000
显然,您可以将所需的任何原子操作添加到您的 Objective-C 代码中(我只实现atomic_fetch_add
了 ,但希望它说明了这个想法)。
就个人而言,我会坚持使用更传统的 Swift 模式,但如果您真的想使用建议的替代 OSAtomic,那么实现可能看起来像这样。
推荐阅读
- python - 如何从之前删除的 Conda 中恢复环境?
- javascript - React Native Alert 自动隐藏和消失
- python-3.x - 当字典中不存在特定键时
- jenkins - 通过解析 webhook 请求正文/JSONpath 参数化我的 repo url
- arrays - 有没有一种有效的方法可以按顺序从排序的向量中删除元素?
- c++ - 当结构是赋值的右值时,返回结构内变量的值
- python - 如何使用 Keras 的深度学习模型解决不适合 imagenet 数据集的问题?
- postgresql - Postgres Timescale DB 中的更新
- android - flutter build apk --split-per-abi:app-armeabi-v7a-release.apk和app.apk有什么区别
- reactjs - 样式不适用于 css 组合器 + 带有 makeStyles() material-ui 的伪类