Apakah variabel Swift bersifat atom?


102

Di Objective-C Anda memiliki perbedaan antara sifat atom dan nonatomik:

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

Dari pemahaman saya, Anda dapat membaca dan menulis properti yang didefinisikan sebagai atom dari banyak utas dengan aman, sementara menulis dan mengakses properti nonatomik atau ivars dari beberapa utas pada saat yang sama dapat mengakibatkan perilaku yang tidak ditentukan, termasuk kesalahan akses yang buruk.

Jadi jika Anda memiliki variabel seperti ini di Swift:

var object: NSObject

Dapatkah saya membaca dan menulis ke variabel ini secara paralel dengan aman? (Tanpa mempertimbangkan arti sebenarnya dari melakukan ini).


Saya pikir di masa depan, mungkin kita bisa menggunakan @atomicatau @nonatomic. atau hanya atom secara default. (Swift sangat tidak lengkap, kami tidak tahu banyak sekarang)
Bryan Chen

1
IMO, mereka akan membuat semuanya non-atomik secara default, dan mungkin menyediakan fitur khusus untuk membuat barang atom.
eonil

Selain itu, atomicumumnya tidak dianggap cukup untuk interaksi aman thread dengan properti, kecuali untuk tipe data sederhana. Untuk objek, seseorang biasanya menyinkronkan akses lintas utas menggunakan kunci (mis., NSLockAtau @synchronized) atau antrean GCD (mis., Antrean serial atau antrean serentak dengan pola "pembaca-penulis").
Rob

@Rob, benar, meskipun karena penghitungan referensi di Objective-C (dan mungkin di Swift) pembacaan dan penulisan secara bersamaan ke variabel tanpa akses atom dapat mengakibatkan kerusakan memori. Jika semua variabel memiliki akses atomik, hal terburuk yang dapat terjadi akan menjadi kondisi balapan "logis", yaitu perilaku yang tidak terduga.
lassej

Jangan salah paham: Saya harap Apple menjawab / menyelesaikan pertanyaan perilaku atom. Hanya saja (a) atomictidak memastikan keamanan thread untuk objek; dan (b) jika seseorang menggunakan salah satu teknik sinkronisasi yang disebutkan di atas dengan benar untuk memastikan keamanan thread (antara lain, mencegah baca / tulis secara bersamaan), masalah atomik diperdebatkan. Tetapi kita masih membutuhkan / menginginkannya untuk tipe data sederhana, yang atomicmemiliki nilai nyata. Pertanyaan bagus!
Rob pada

Jawaban:


52

Sangat awal untuk mengasumsikan karena tidak ada dokumentasi tingkat rendah yang tersedia, tetapi Anda dapat belajar dari perakitan. Hopper Disassembler adalah alat yang hebat.

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

Menggunakan objc_storeStrongdan objc_setProperty_atomicuntuk nonatomik dan atom, di mana

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

penggunaan swift_retaindari libswift_stdlib_coredan, tampaknya, tidak dilengkapi pengaman benang.

Kami dapat berspekulasi bahwa kata kunci tambahan (mirip dengan @lazy) mungkin diperkenalkan nanti.

Pembaruan 07/20/15 : menurut posting blog ini di lingkungan cepat lajang dapat membuat kasus-kasus tertentu aman untuk Anda, yaitu:

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

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

}

Pembaruan 05/25/16 : Perhatikan proposal evolusi cepat https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - sepertinya begitu akan mungkin memiliki @atomicperilaku yang diterapkan sendiri.


Saya memperbarui jawaban saya dengan beberapa info terbaru, semoga membantu
Sash Zats

1
Hai, terima kasih untuk tautan ke alat Hopper Disassembler. Terlihat rapi.
C0D3

11

Swift tidak memiliki konstruksi bahasa seputar keamanan thread. Diasumsikan bahwa Anda akan menggunakan library yang disediakan untuk melakukan manajemen keamanan thread Anda sendiri. Ada banyak opsi yang Anda miliki dalam mengimplementasikan keamanan thread termasuk pthread mutexes, NSLock, dan dispatch_sync sebagai mekanisme mutex. Lihat posting terbaru Mike Ash tentang subjek: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html Jadi jawaban langsung untuk pertanyaan Anda tentang "Can Saya membaca dan menulis ke variabel ini secara paralel dengan aman? " Tidak.


7

Mungkin terlalu dini untuk menjawab pertanyaan ini. Saat ini swift tidak memiliki pengubah akses, jadi tidak ada cara yang jelas untuk menambahkan kode yang mengelola konkurensi di sekitar pengambil / penyetel properti. Selain itu, Bahasa Swift sepertinya belum memiliki informasi tentang konkurensi! (Itu juga tidak memiliki KVO dll ...)

Saya pikir jawaban atas pertanyaan ini akan menjadi jelas di rilis mendatang.


re: kurangnya KVO, lihat willSet, didSet- tampaknya menjadi langkah pertama dalam perjalanan
Sash Zats

1
willSet, didSet lebih untuk properti yang selalu membutuhkan penyetel kustom karena harus melakukan sesuatu. Misalnya, properti warna yang perlu menggambar ulang tampilan saat properti diubah ke nilai yang berbeda; yang sekarang dilakukan lebih mudah menggunakan didSet.
gnasher729

ya, itulah yang saya maksud dengan "langkah pertama" :) Saya berasumsi bahwa ini mungkin merupakan tanda bahwa fitur telah tersedia tetapi belum sepenuhnya diterapkan
Sash Zats

6

Detail

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

Tautan

Jenis yang diterapkan

Ide utama

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

Contoh akses atom

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

Pemakaian

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

Hasil

masukkan deskripsi gambar di sini


Proyek sampel di github akan menyenangkan!
Klaas

1
Halo! Ini adalah sampel lengkap. Salin Atomickelas dan jalankan menggunakanAtomic().semaphoreSample()
Vasily Bodnarchuk

Ya, saya sudah melakukannya. Saya pikir akan menyenangkan, menjadikannya sebagai proyek yang diperbarui ke sintaks terbaru. Dengan Swift, sintaksnya berubah setiap saat. Dan jawaban Anda sejauh ini adalah yang terbaru :)
Klaas

1

Dari Swift 5.1 Anda dapat menggunakan pembungkus properti untuk membuat logika khusus untuk properti Anda. Ini adalah implementasi atomic wrapper:

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

Cara Penggunaan:

class Shared {
    @atomic var value: Int
...
}

0

Berikut adalah pembungkus properti atom yang saya gunakan secara ekstensif. Saya membuat mekanisme penguncian yang sebenarnya sebagai protokol, jadi saya bisa bereksperimen dengan mekanisme yang berbeda. Saya mencoba semaphores DispatchQueues,, dan pthread_rwlock_t. Itu pthread_rwlock_tdipilih karena tampaknya memiliki overhead terendah, dan kemungkinan inversi prioritas yang lebih rendah.

/// 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()
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.