Swift değişkenleri atomik midir?


102

Objective-C'de atomik ve atomik olmayan özellikler arasında bir ayrım var:

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

Anladığım kadarıyla, atomik olarak tanımlanan özellikleri birden çok iş parçacığından güvenli bir şekilde okuyabilir ve yazabilirsiniz, aynı anda birden çok iş parçacığından atomik olmayan özellikleri veya ivarları yazıp bunlara erişmek, kötü erişim hataları da dahil olmak üzere tanımlanmamış davranışlara neden olabilir.

Yani Swift'de buna benzer bir değişkeniniz varsa:

var object: NSObject

Bu değişkeni güvenle paralel olarak okuyup yazabilir miyim? (Bunu yapmanın gerçek anlamını düşünmeden).


Bence gelecekte belki kullanabiliriz @atomicveya @nonatomic. veya varsayılan olarak sadece atomik. (Swift o kadar eksik ki şu anda pek bir şey söyleyemeyiz)
Bryan Chen

1
IMO, her şeyi varsayılan olarak atomik olmayan yapacak ve muhtemelen atomik şeyler yapmak için özel bir özellik sağlayacaklar.
eonil

Öte yandan, atomicbasit veri türleri haricinde, genellikle bir özellik ile iş parçacığı güvenli etkileşim için yeterli görülmez. Nesneler için, genel olarak kilitler (ör. NSLockVeya @synchronized) veya GCD kuyrukları (ör. Seri sıra veya "okuyucu-yazar" kalıbı ile eşzamanlı sıra) kullanılarak iş parçacıkları arasında erişim senkronize edilir .
Rob

@Rob, doğru, ancak Objective-C'deki (ve muhtemelen Swift'deki) referans sayımı nedeniyle atomik erişim olmadan bir değişkene eşzamanlı okuma ve yazma işlemi bellek bozulmasına neden olabilir. Tüm değişkenler atomik erişime sahip olsaydı, olabilecek en kötü şey "mantıksal" bir yarış koşulu, yani beklenmeyen davranış olurdu.
lassej

Beni yanlış anlamayın: Umarım Apple atomik davranış sorusunu yanıtlar / çözer. Sadece (a) atomicnesneler için iplik güvenliğini sağlamaz; ve (b) iş parçacığı güvenliğini sağlamak için (diğer şeylerin yanı sıra, eşzamanlı okumayı / yazmayı engelleyen) yukarıda bahsedilen senkronizasyon tekniklerinden birini uygun şekilde kullanırsa, atomik sorun tartışmalıdır. Ama yine atomicde gerçek değeri olan basit veri türleri için buna ihtiyacımız var / istiyoruz . İyi soru!
Rob

Yanıtlar:


52

Düşük seviyeli dokümantasyon bulunmadığını varsaymak için çok erken, ancak montajdan çalışabilirsiniz. Hopper Disassembler harika bir araçtır.

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

Kullanımları objc_storeStrongve objc_setProperty_atomicnonatomic ve atomik sırasıyla nereye

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

kullanımları swift_retaingelen libswift_stdlib_coreve görünüşe göre, iplik emniyet inşa yoktur.

Ek anahtar kelimelerin (ile benzer @lazy) daha sonra kullanılabileceğini tahmin edebiliriz .

Güncelleme 07/20/15 : singletons'daki bu blog gönderisine göre hızlı ortam, belirli durumları sizin için güvenli hale getirebilir, yani:

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

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

}

Güncelleme 05/25/16 : Hızlı evrim önerisine göz kulak olun https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - öyle görünüyor kendi başına bir @atomicdavranışa sahip olmak mümkün olacak .


Cevabımı bazı yeni bilgilerle güncelledim, umarım yardımcı olur
Sash Zats

1
Hey, Hopper Disassembler aracına bağlantı için teşekkürler. Güzel görünüyor.
C0D3

11

Swift'in iş parçacığı güvenliği etrafında dil yapısı yoktur. Kendi iş parçacığı güvenliği yönetiminizi yapmak için sağlanan kitaplıkları kullanacağınız varsayılmaktadır. Bir muteks mekanizması olarak pthread muteksleri, NSLock ve dispatch_sync dahil olmak üzere iş parçacığı güvenliğini uygulamada sahip olduğunuz çok sayıda seçenek vardır. Mike Ash'in konuyla ilgili son gönderisine bakın: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html Yani "Can" Bu değişkeni güvenle paralel olarak okuyup yazıyorum? " hayır.


7

Bu soruyu cevaplamak için muhtemelen erken. Şu anda swift, erişim değiştiricilerden yoksundur, bu nedenle bir özellik alıcı / ayarlayıcı etrafında eşzamanlılığı yöneten kod eklemenin açık bir yolu yoktur. Dahası, Swift Dili henüz eşzamanlılık hakkında herhangi bir bilgiye sahip görünmüyor! (Ayrıca KVO vb. Yoksun ...)

Bu sorunun cevabının gelecek sürümlerde netleşeceğini düşünüyorum.


re: KVO eksikliği, kontrol edin willSet, didSet- bu yolda ilk adım gibi görünüyor
Sash Zats

1
willSet, didSet, bir şeyler yapmak zorunda oldukları için her zaman özel bir ayarlayıcıya ihtiyaç duyan özellikler için daha fazladır. Örneğin, özellik farklı bir değere değiştirildiğinde görünümü yeniden çizmesi gereken bir renk özelliği; bu artık didSet kullanılarak daha kolay yapılır.
gnasher729

evet, "ilk adım" derken kastettiğim buydu :) Özelliğin kullanılabilir olduğunun ancak henüz tam olarak uygulanmadığının bir işareti olabileceğini varsaydım
Sash Zats

6

Detaylar

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

Bağlantılar

Uygulanan türler

Ana fikir

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
        }
    }
}

Atomik erişim örneği

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

Kullanım

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

Sonuç

görüntü açıklamasını buraya girin


Github'da örnek bir proje güzel olurdu!
Klaas

1
Merhaba! Bu tam bir örnektir. AtomicSınıfı kopyalayın ve kullanarak çalıştırınAtomic().semaphoreSample()
Vasily Bodnarchuk

Evet, zaten yaptım. En güncel sözdizimine güncellenen bir proje olarak sahip olmanın güzel olacağını düşündüm. Swift ile sözdizimi her zaman değişir. Cevabınız açık ara en
yenisi

1

İşte yoğun olarak kullandığım atomik özellik sarmalayıcı. Gerçek kilitleme mekanizmasını bir protokol haline getirdim, böylece farklı mekanizmaları deneyebilirim. Semaforları denedim DispatchQueuesve pthread_rwlock_t. En pthread_rwlock_tdüşük ek yüke ve daha düşük öncelikli ters çevirme şansına sahip olduğu için seçildi.

/// 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()
    }
}

1

Swift 5.1'den, mülkleriniz için belirli bir mantık oluşturmak için özellik sarmalayıcıları kullanabilirsiniz . Bu atomik sarmalayıcı uygulamasıdır:

@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
    }
}

Nasıl kullanılır:

class Shared {
    @atomic var value: Int
...
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.