質問は最新の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命令を生成する任意の言語で記述されている可能性があります。
編集2OSAtomicインターフェースは現在非推奨ですが、どのような置き換えでも、ほぼ同じ機能と同じ動作が舞台裏で行われると思います。したがって、32ビット値と64ビット値を安全に読み取ることができるかどうかという問題は依然として存在します。
GitHubとここSOでも循環する誤った実装の3つの注意を編集してください:値の読み取りも安全な方法で行う必要があります(以下のRobの回答を参照)
このOSAtomic
APIは非推奨です。ドキュメントにはそれが記載されておらず、Swiftからの警告は表示されませんが、Objective-Cから使用すると、非推奨の警告が表示されます。
'OSAtomicCompareAndSwap64Barrier'は非推奨になりました:iOS10で最初に非推奨になりました-代わりにatomic_compare_exchange_strong()を使用してください
(macOSで作業している場合は、macOS 10.12で非推奨になったことを警告します。)
Swiftで変数をアトミックにインクリメントするにはどうすればよいですか?を参照してください。
あなたは尋ねました:
OSAtomicインターフェースは現在非推奨ですが、どのような置き換えでも、ほぼ同じ機能と同じ動作が舞台裏で行われると思います。したがって、32ビット値と64ビット値を安全に読み取ることができるかどうかという問題は依然として存在します。
推奨される交換はstdatomic.h
です。それは持っているatomic_load
方法を、私は直接アクセスするのではなく、それを使用します。
個人的には、を使用しないことをお勧めしますOSAtomic
。Objective-Cからはstdatomic.h
、の使用を検討できますが、Swiftからは、GCDシリアルキュー、GCDリーダー/ライターパターン、またはNSLock
ベースのアプローチなど、標準の一般的な同期メカニズムの1つを使用することをお勧めします。従来の通念では、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)
}
}
}
この小さな拡張機能(AppleのwithCriticalSection
方法に触発された)を使用して、より簡単な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
、これは単純なロードとストアのみを対象としています。ロード、インクリメント、ストア全体を1つのタスクとして同期する必要があるため、ここでは使用していません。だから私はこのsynchronized
方法を使っています。次のことを考慮してください。
DispatchQueue.concurrentPerform(iterations: 1_000_000) { _ in
foo.value += 1
}
print(foo.value) // not 1,000,000 !!!
同期value
アクセサーを使用しているため、妥当に見えます。ただし、同期ロジックのレベルが間違っているため、機能しません。この値のロード、インクリメント、およびストアを個別に同期するのではなく、3つのステップすべてをすべて一緒に同期する必要があります。したがって、前の例に示したように、全体value += 1
をsynchronized
クロージャー内にラップして、目的の動作を実現します。
ちなみに、並行性とプロパティラッパーにキューとセマフォを使用しますか?を参照してください。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の代替案を本当に使用したい場合は、これが実装のように見える可能性があります。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加