Swift変数はアトミックですか?


102

Objective-Cでは、アトミックプロパティと非アトミックプロパティの違いがあります。

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

私の理解では、複数のスレッドからアトミックとして定義されたプロパティを安全に読み書きできる一方で、複数のスレッドから非アトミックプロパティまたはivarに同時に書き込みおよびアクセスすると、不正なアクセスエラーなど、未定義の動作が発生する可能性があります。

したがって、Swiftに次のような変数がある場合:

var object: NSObject

安全にこの変数を並行して読み書きできますか?(これを行うことの実際の意味を考慮せずに)。


将来的には、@atomicまたはを使用できると思います@nonatomic。またはデフォルトでアトミック。(Swiftは非常に不完全なので、今はあまり説明できません)
ブライアンチェン

1
IMO、彼らはデフォルトですべてを非アトミックにし、おそらくアトミックなものを作るための特別な機能を提供します。
eonil 2014年

余談ですatomicが、単純なデータ型を除いて、プロパティとのスレッドセーフな対話には一般的に十分とは見なされません。オブジェクトの場合、一般に、ロック(NSLockまたは、または@synchronized)またはGCDキュー(たとえば、シリアルキューまたは「リーダーライター」パターンの同時キュー)を使用して、スレッド間でアクセスを同期します。
Rob

@ Rob、true、ただしObjective-C(およびおそらくSwift)での参照カウントのため、アトミックアクセスなしで変数への同時読み取りおよび書き込みを行うと、メモリが破損する可能性があります。すべての変数がアトミックアクセスを持っている場合、発生する可能性のある最悪の事態は「論理的な」競合状態、つまり予期しない動作です。
lassej 2014

誤解しないでください。Appleがアトミックな動作の質問に回答/解決することを願っています。(a)atomicオブジェクトのスレッドセーフ性が保証されていないだけです。(b)前述の同期手法の1つを適切に使用してスレッドの安全性を確保する場合(とりわけ、同時読み取り/書き込みを防止する場合)、アトミックな問題は疑わしいものです。しかし、atomic本当の価値がある単純なデータ型については、まだ必要としています。良い質問!
Rob

回答:


52

低レベルのドキュメントがないので、想定するのは非常に早いですが、アセンブリから学習することができます。ホッパー逆アセンブラーは素晴らしいツールです。

@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シングルトンに関するこのブログポストによれば、迅速な環境で特定のケースをスレッドセーフにすることができます。

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自分で動作を実装できるようになります。


私はそれが役に立てば幸い、最近のいくつかの情報と私の答えを更新
サッシZats

1
ホッパー逆アセンブラーツールへのリンクをありがとう。きちんと見えます。
C0D3 2016年

11

Swiftには、スレッドセーフに関する言語構造はありません。提供されたライブラリを使用して独自のスレッドセーフティ管理を行うことを前提としています。ミューテックスメカニズムとしてのpthreadミューテックス、NSLock、dispatch_syncなど、スレッドセーフティの実装には多数のオプションがあります。:被写体にマイク・アッシュの最近の記事を参照してくださいhttps://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html ので缶」のあなたの質問に直接答え安全にこの変数を読み書きしますか?」いいえ


7

この質問に答えることはおそらく早いです。現在、swiftにはアクセス修飾子がないため、プロパティゲッター/セッターの同時実行性を管理するコードを追加する明確な方法はありません。さらに、Swift言語には並行性に関する情報がまだないようです!(KVOなども欠けています...)

この質問に対する答えは、今後のリリースで明らかになると思います。


再:KVOの欠如、チェックアウトwillSetdidSet-道の最初のステップのように見える
サッシZats

1
willSet、didSetは、何かをしなければならなかったために常にカスタムセッターを必要とするプロパティ用です。たとえば、プロパティが別の値に変更されたときにビューを再描画する必要があるカラープロパティ。これは、didSetを使用すると簡単に実行できます。
gnasher729 2014年

はい、それは私が「最初のステップ」で意味したものです:)それは機能が利用可能であるがまだ完全に実装されていない兆候かもしれないと
思いました

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()
Vasily Bodnarchuk '28

はい、すでにしました。最新の構文に更新されるプロジェクトとして持つのは良いことだと思いました。Swiftでは、構文は常に変化します。そして、あなたの答えは断然最新のものです:)
Klaas

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

0

以下は、私が頻繁に使用するアトミックプロパティラッパーです。実際のロックメカニズムをプロトコルにしたので、さまざまなメカニズムを試すことができました。セマフォを試してみましDispatchQueuespthread_rwlock_tpthread_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()
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.