Атомарны ли переменные Swift?

102

В Objective-C у вас есть различие между атомарными и неатомарными свойствами:

@property (nonatomic, strong) NSObject *nonatomicObject;
@property (atomic, strong) NSObject *atomicObject;

Насколько я понимаю, вы можете безопасно читать и записывать свойства, определенные как атомарные, из нескольких потоков, в то время как одновременная запись и доступ к неатомным свойствам или ivars из нескольких потоков может привести к неопределенному поведению, включая ошибки неправильного доступа.

Итак, если у вас есть такая переменная в Swift:

var object: NSObject

Могу ли я безопасно читать и записывать в эту переменную параллельно? (Без учета фактического значения этого).

лассей
источник
Думаю, в будущем мы сможем использовать @atomicили @nonatomic. или просто атомарно по умолчанию. (Swift настолько неполный, что мы не можем сейчас сказать много)
Брайан Чен
1
IMO, они по умолчанию сделают все неатомарным и, вероятно, предоставят специальную функцию для создания атомарных вещей.
eonil
В стороне, atomicобычно не считается достаточным для поточно-безопасного взаимодействия со свойством, за исключением простых типов данных. Для объектов обычно синхронизируют доступ между потоками с использованием блокировок (например, NSLockили @synchronized) или очередей GCD (например, последовательной очереди или параллельной очереди с шаблоном «читатель-писатель»).
Роб
@Rob, правда, хотя из-за подсчета ссылок в Objective-C (и, возможно, в Swift) одновременное чтение и запись в переменную без атомарного доступа может привести к повреждению памяти. Если бы все переменные имели атомарный доступ, худшим, что могло бы случиться, было бы «логическое» состояние гонки, то есть неожиданное поведение.
lassej 05
Не поймите меня неправильно: я надеюсь, что Apple ответит / решит вопрос об атомарном поведении. Просто (а) atomicне обеспечивает потокобезопасность для объектов; и (б) если один должным образом использует одну из вышеупомянутых методов синхронизации , чтобы обеспечить безопасность потоков (среди прочего, предотвращая одновременное чтение / запись), атомный вопрос , является спорным. Но он нам все еще нужен / нужен для простых типов данных, которые atomicимеют реальную ценность. Хороший вопрос!
Роб

Ответы:

52

Очень рано предполагать, что нет низкоуровневой документации, но вы можете изучить ее на сборке. Hopper Disassembler - отличный инструмент.

@interface ObjectiveCar : NSObject
@property (nonatomic, strong) id engine;
@property (atomic, strong) id driver;
@end

Использует objc_storeStrongи objc_setProperty_atomicдля неатомных и атомных соответственно, где

class SwiftCar {
    var engine : AnyObject?    
    init() {
    }
}

использует swift_retainfrom libswift_stdlib_coreи, по-видимому, не имеет встроенной защиты потоков.

Мы можем предположить, что дополнительные ключевые слова (похожие на @lazy) могут быть введены позже.

Обновление от 20.07.15 : согласно этому сообщению в блоге о синглтонах, быстрая среда может сделать некоторые случаи потокобезопасными для вас, например:

class Car {
    static let sharedCar: Car = Car() // will be called inside of dispatch_once
}

private let sharedCar: Car2 = Car2() // same here
class Car2 {

}

Обновление 25.05.16 : следите за предложением быстрой эволюции https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - похоже, что это так будет возможно @atomicреализовать поведение самостоятельно.

Кушак Зац
источник
Я обновил свой ответ недавней информацией, надеюсь, это поможет
Саш Зац
1
Привет, спасибо за ссылку на инструмент Hopper Disassembler. Выглядит аккуратно.
C0D3
11

Swift не имеет языковых конструкций для обеспечения безопасности потоков. Предполагается, что вы будете использовать предоставленные библиотеки для собственного управления безопасностью потоков. Существует множество вариантов реализации безопасности потоков, включая мьютексы pthread, NSLock и dispatch_sync в качестве механизма мьютекса. См. Недавнюю публикацию Майка Эша на эту тему: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html. Итак, прямой ответ на ваш вопрос «Может ли Я безопасно читаю и записываю в эту переменную параллельно? " нет.

Добрый Дуг
источник
7

На этот вопрос, наверное, еще рано отвечать. В настоящее время в swift отсутствуют модификаторы доступа, поэтому не существует очевидного способа добавить код, который управляет параллелизмом, вокруг средства получения / установки свойств. Более того, похоже, что в Swift Language еще нет информации о параллелизме! (В нем также отсутствует KVO и т. Д ...)

Думаю, ответ на этот вопрос станет ясен в будущих выпусках.

Колин
источник
re: отсутствие KVO, проверьте willSet, didSet- кажется, первый шаг на пути
Sash Zats
1
willSet, didSet больше подходит для свойств, которые всегда нуждались в настраиваемом сеттере, потому что им нужно было что-то делать. Например, свойство цвета, которое требует перерисовки представления, когда свойство изменяется на другое значение; Теперь это сделать проще с помощью didSet.
gnasher729
да, это то, что я имел в виду под "первым шагом" :) Я предположил, что это может быть признаком того, что функция доступна, но еще не полностью реализована
Саш Затс
6

подробности

  • Xcode 9.1, Swift 4
  • Xcode 10.2.1 (10E1001), Swift 5

Ссылки

Реализованные типы

Главная мысль

class Example {

    private lazy var semaphore = DispatchSemaphore(value: 1)

    func executeThreadSafeFunc1() {
        // Lock access. Only first thread can execute code below.
        // Other threads will wait until semaphore.signal() will execute
        semaphore.wait()
        // your code
        semaphore.signal()         // Unlock access
    }

    func executeThreadSafeFunc2() {
        // Lock access. Only first thread can execute code below.
        // Other threads will wait until semaphore.signal() will execute
        semaphore.wait()
        DispatchQueue.global(qos: .background).async {
            // your code
            self.semaphore.signal()         // Unlock access
        }
    }
}

Пример атомарного доступа

class Atomic {

    let dispatchGroup = DispatchGroup()
    private var variable = 0

    // Usage of semaphores

    func semaphoreSample() {

        // value: 1 - number of threads that have simultaneous access to the variable
        let atomicSemaphore = DispatchSemaphore(value: 1)
        variable = 0

        runInSeveralQueues { dispatchQueue  in
            // Only (value) queqes can run operations betwen atomicSemaphore.wait() and atomicSemaphore.signal()
            // Others queues await their turn
            atomicSemaphore.wait()            // Lock access until atomicSemaphore.signal()
            self.variable += 1
            print("\(dispatchQueue), value: \(self.variable)")
            atomicSemaphore.signal()          // Unlock access
        }

        notifyWhenDone {
            atomicSemaphore.wait()           // Lock access until atomicSemaphore.signal()
            print("variable = \(self.variable)")
            atomicSemaphore.signal()         // Unlock access
        }
    }

    // Usage of sync of DispatchQueue

    func dispatchQueueSync() {
        let atomicQueue = DispatchQueue(label: "dispatchQueueSync")
        variable = 0

        runInSeveralQueues { dispatchQueue  in

            // Only queqe can run this closure (atomicQueue.sync {...})
            // Others queues await their turn
            atomicQueue.sync {
                self.variable += 1
                print("\(dispatchQueue), value: \(self.variable)")
            }
        }

        notifyWhenDone {
            atomicQueue.sync {
                print("variable = \(self.variable)")
            }
        }
    }

    // Usage of objc_sync_enter/objc_sync_exit

    func objcSync() {
        variable = 0

        runInSeveralQueues { dispatchQueue  in

            // Only one queqe can run operations betwen objc_sync_enter(self) and objc_sync_exit(self)
            // Others queues await their turn
            objc_sync_enter(self)                   // Lock access until objc_sync_exit(self).
            self.variable += 1
            print("\(dispatchQueue), value: \(self.variable)")
            objc_sync_exit(self)                    // Unlock access
        }

        notifyWhenDone {
            objc_sync_enter(self)                   // Lock access until objc_sync_exit(self)
            print("variable = \(self.variable)")
            objc_sync_exit(self)                    // Unlock access
        }
    }
}

// Helpers

extension Atomic {

    fileprivate func notifyWhenDone(closure: @escaping ()->()) {
        dispatchGroup.notify(queue: .global(qos: .utility)) {
            closure()
            print("All work done")
        }
    }

    fileprivate func runInSeveralQueues(closure: @escaping (DispatchQueue)->()) {

        async(dispatch: .main, closure: closure)
        async(dispatch: .global(qos: .userInitiated), closure: closure)
        async(dispatch: .global(qos: .utility), closure: closure)
        async(dispatch: .global(qos: .default), closure: closure)
        async(dispatch: .global(qos: .userInteractive), closure: closure)
    }

    private func async(dispatch: DispatchQueue, closure: @escaping (DispatchQueue)->()) {

        for _ in 0 ..< 100 {
            dispatchGroup.enter()
            dispatch.async {
                let usec = Int(arc4random()) % 100_000
                usleep(useconds_t(usec))
                closure(dispatch)
                self.dispatchGroup.leave()
            }
        }
    }
}

использование

Atomic().semaphoreSample()
//Atomic().dispatchQueueSync()
//Atomic().objcSync()

Результат

введите описание изображения здесь

Василий Боднарчук
источник
Было бы неплохо создать образец проекта на github!
Klaas
1
Здравствуйте! Это полный образец. Скопируйте Atomicкласс и запустите его с помощьюAtomic().semaphoreSample()
Василий Боднарчук
Да, уже сделал. Подумал, что было бы неплохо, если бы он был обновлен до последней версии синтаксиса. В Swift синтаксис все время меняется. И ваш ответ, безусловно, самый последний :)
Klaas
1

Вот оболочка атомарных свойств, которую я широко использую. Я сделал реальный механизм блокировки протоколом, чтобы я мог экспериментировать с разными механизмами. Я пробовал семафоры DispatchQueues, и pthread_rwlock_t. pthread_rwlock_tБыл выбран потому , что , как представляется , имеют самую низкую нагрузку, и меньше шансов на инверсии приоритетов.

/// Defines a basic signature that all locks will conform to. Provides the basis for atomic access to stuff.
protocol Lock {
    init()
    /// Lock a resource for writing. So only one thing can write, and nothing else can read or write.
    func writeLock()
    /// Lock a resource for reading. Other things can also lock for reading at the same time, but nothing else can write at that time.
    func readLock()
    /// Unlock a resource
    func unlock()
}

final class PThreadRWLock: Lock {
    private var rwLock = pthread_rwlock_t()

    init() {
        guard pthread_rwlock_init(&rwLock, nil) == 0 else {
            preconditionFailure("Unable to initialize the lock")
        }
    }

    deinit {
        pthread_rwlock_destroy(&rwLock)
    }

    func writeLock() {
        pthread_rwlock_wrlock(&rwLock)
    }

    func readLock() {
        pthread_rwlock_rdlock(&rwLock)
    }

    func unlock() {
        pthread_rwlock_unlock(&rwLock)
    }
}

/// A property wrapper that ensures atomic access to a value. IE only one thing can write at a time.
/// Multiple things can potentially read at the same time, just not during a write.
/// By using `pthread` to do the locking, this safer then using a `DispatchQueue/barrier` as there isn't a chance
/// of priority inversion.
@propertyWrapper
public final class Atomic<Value> {

    private var value: Value
    private let lock: Lock = PThreadRWLock()

    public init(wrappedValue value: Value) {
        self.value = value
    }

    public var wrappedValue: Value {
        get {
            self.lock.readLock()
            defer { self.lock.unlock() }
            return self.value
        }
        set {
            self.lock.writeLock()
            self.value = newValue
            self.lock.unlock()
        }
    }

    /// Provides a closure that will be called synchronously. This closure will be passed in the current value
    /// and it is free to modify it. Any modifications will be saved back to the original value.
    /// No other reads/writes will be allowed between when the closure is called and it returns.
    public func mutate(_ closure: (inout Value) -> Void) {
        self.lock.writeLock()
        closure(&value)
        self.lock.unlock()
    }
}
Jamone
источник
1

Начиная с Swift 5.1, вы можете использовать оболочки свойств для создания определенной логики для ваших свойств. Это реализация атомарной оболочки:

@propertyWrapper
struct atomic<T> {
    private var value: T
    private let lock = NSLock()

    init(wrappedValue value: T) {
        self.value = value
    }

    var wrappedValue: T {
      get { getValue() }
      set { setValue(newValue: newValue) }
    }

    func getValue() -> T {
        lock.lock()
        defer { lock.unlock() }

        return value
    }

    mutating func setValue(newValue: T) {
        lock.lock()
        defer { lock.unlock() }

        value = newValue
    }
}

Как пользоваться:

class Shared {
    @atomic var value: Int
...
}
iUrii
источник