106 votes

Les variables Swift sont-elles atomiques ?

En Objective-C, on distingue les propriétés atomiques et non atomiques :

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

D'après ce que j'ai compris, vous pouvez lire et écrire des propriétés définies comme atomiques à partir de plusieurs threads en toute sécurité, tandis que l'écriture et l'accès à des propriétés non atomiques ou à des ivars à partir de plusieurs threads en même temps peuvent entraîner un comportement non défini, y compris des erreurs de mauvais accès.

Donc si vous avez une variable comme ceci dans Swift :

var object: NSObject

Puis-je lire et écrire dans cette variable en parallèle en toute sécurité ? (Sans tenir compte de la signification réelle de cette opération).

0 votes

Je pense qu'à l'avenir, nous pourrons peut-être utiliser @atomic o @nonatomic . ou simplement atomique par défaut. (Swift est tellement incomplet qu'on ne peut pas dire grand chose pour l'instant)

1 votes

IMO, ils rendront tout non-atomique par défaut, et fourniront probablement une fonction spéciale pour rendre les choses atomiques.

0 votes

En passant, atomic n'est généralement pas considéré comme suffisant pour une interaction sûre avec une propriété, sauf pour les types de données simples. Pour les objets, on synchronise généralement l'accès entre les threads en utilisant des verrous (par ex, NSLock o @synchronized ) ou des files d'attente GCD (par exemple, une file d'attente série ou une file d'attente concurrente avec un modèle "lecteur-écrivain").

54voto

Sash Zats Points 815

Il est encore trop tôt pour se prononcer car aucune documentation de bas niveau n'est disponible, mais vous pouvez étudier à partir de l'assemblage. Démonteur de trémie est un outil formidable.

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

Utilisations objc_storeStrong y objc_setProperty_atomic pour le non atomique et l'atomique respectivement, où

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

utilise swift_retain de libswift_stdlib_core et, apparemment, n'a pas de sécurité intégrée.

Nous pouvons supposer que des mots-clés supplémentaires (similaires à @lazy ) pourraient être introduits ultérieurement.

Mise à jour 07/20/15 selon ce qui suit blogpost sur les singletons swift environment peut rendre certains cas thread safe pour vous, c'est-à-dire :

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

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

}

Mise à jour 05/25/16 : Gardez un œil sur la proposition d'évolution rapide https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - il semble qu'il va être possible d'avoir @atomic comportement mis en œuvre par vous-même.

0 votes

J'ai mis à jour ma réponse avec des informations récentes, j'espère que cela vous aidera.

1 votes

Hé, merci pour le lien vers l'outil de désassemblage de Hopper. Ca a l'air chouette.

11voto

Good Doug Points 2002

Swift n'a pas de construction de langage autour de la sécurité des threads. Il est supposé que vous utiliserez les bibliothèques fournies pour gérer vous-même la sécurité des threads. Vous disposez d'un grand nombre d'options pour mettre en œuvre la thread safety, notamment les mutex pthread, NSLock et dispatch_sync comme mécanisme de mutex. Voir le récent billet de Mike Ash sur le sujet : https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html La réponse directe à votre question "Puis-je lire et écrire dans cette variable en parallèle en toute sécurité ?" est donc non.

7voto

ColinE Points 36907

Il est probablement trop tôt pour répondre à cette question. Actuellement, Swift ne dispose pas de modificateurs d'accès, il n'y a donc pas de moyen évident d'ajouter du code qui gère la concurrence autour d'un getter / setter de propriétés. De plus, le langage Swift ne semble pas encore avoir d'informations sur la concurrence ! (Il manque aussi le KVO etc ...)

Je pense que la réponse à cette question sera claire dans les prochaines versions.

0 votes

Re : manque de KVO, regardez willSet , didSet - semblent être un premier pas sur la voie

1 votes

WillSet, didSet est plutôt destiné aux propriétés qui ont toujours eu besoin d'un setter personnalisé parce qu'elles devaient faire quelque chose. Par exemple, une propriété de couleur qui doit redessiner une vue lorsque la propriété est changée en une valeur différente ; cela est maintenant plus facile en utilisant didSet.

0 votes

Oui, c'est ce que je voulais dire par "un premier pas" :) J'ai supposé que cela pouvait être un signe que la fonctionnalité était disponible mais pas encore complètement implémentée.

7voto

Vasily Bodnarchuk Points 8047

Détails

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

Liens

Types mis en œuvre

Idée principale

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

Exemple d'accès atomique

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

Utilisation

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

Résultat

enter image description here

6voto

iUrii Points 11

À partir de Swift 5.1, vous pouvez utiliser enveloppes de propriété pour créer une logique spécifique à vos propriétés. Il s'agit de l'implémentation d'un wrapper atomique :

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

Comment l'utiliser :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X