Являются ли переменные Свифта атомарными?

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

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

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

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

var object: NSObject

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

Ответ 1

Очень рано предполагать, что документация низкого уровня недоступна, но вы можете учиться на сборке. 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_retain от libswift_stdlib_core и, по-видимому, не имеет встроенной защиты потока.

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

Обновление 07/20/15: в соответствии с этим blogpost на singleletons быстрая среда может сделать определенные потоки безопасными для вас, то есть:

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

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

}

Обновление 05/25/16. Следите за быстрым предложением о развитии https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - похоже, что он собирается возможно иметь поведение @atomic, реализованное вами самостоятельно.

Ответ 2

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

Ответ 3

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

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

Ответ 4

подробности

  • 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()

Результат

enter image description here