Этот OSAtomic
API устарел. Документация не упоминает об этом, и вы не видите предупреждение от Swift, но при использовании из Objective-C вы получите предупреждения об устаревании:
«OSAtomicCompareAndSwap64Barrier» устарел: сначала устарел в iOS 10- Вместо at
используйте atomic_compare_exchange_strong () (при работе в macOS предупреждает, что в macOS 10.12 он устарел).
См. Как атомарно увеличить aпеременная в Swift?
Вы спросили:
Интерфейс 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)
}
}
}
С этим небольшим расширением (вдохновленным методом withCriticalSection
от Apple) для обеспечения более простого 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
, как показано в предыдущем примере, и достигаем желаемого поведения.
Кстати, смотрите Использование очереди и семафора для параллелизма и свойстваОболочка? для нескольких других реализаций такого рода механизма синхронизации, включая последовательные очереди 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, это то, чтоn реализация может выглядеть так.