Swift变量是原子的吗?


102

在Objective-C中,您可以区分原子性质和非原子性质:

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

根据我的理解,您可以安全地从多个线程读取和写入定义为原子的属性,而同时从多个线程写入和访问非原子属性或ivars可能会导致未定义的行为,包括严重的访问错误。

因此,如果您在Swift中有这样的变量:

var object: NSObject

我可以安全地并行读写该变量吗?(不考虑这样做的实际含义)。


我认为将来也许我们可以使用@atomic@nonatomic。或默认情况下只是原子的。(迅速发展还不完善,我们现在不能说太多了)
Bryan Chen

1
IMO,默认情况下,它们将使所有内容都不是原子性的,并且可能提供了一种特殊功能来制造原子性内容。
2014年

顺便说atomic一句,除了简单的数据类型外,通常认为不足以实现与属性的线程安全交互。对于对象,通常使用锁(例如,NSLock@synchronized)或GCD队列(例如,具有“读写器”模式的串行队列或并发队列)来同步跨线程的访问。
罗布

@Rob,是正确的,尽管由于在Objective-C(可能还有Swift)中引用计数的原因,在没有原子访问的情况下并发读取和写入变量可能会导致内存损坏。如果所有变量都具有原子访问权限,则可能发生的最坏情况将是“逻辑”竞争条件,即意外行为。
lassej 2014年

不要误会我的意思:我希望苹果公司能够回答/解决原子行为问题。只是(a)atomic不能确保对象的线程安全性;(b)如果一个人正确地使用上述同步技术之一来确保线程安全(尤其是防止同时读/写),则根本的问题就不那么重要了。但是我们仍然需要/想要atomic具有实际价值的简单数据类型。好问题!
罗布

Answers:


52

假设尚无底层文档,这还为时过早,但是您可以从汇编中学习。料斗拆卸器是一个很棒的工具。

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

在非原子和原子上分别使用objc_storeStrongobjc_setProperty_atomic,其中

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

使用swift_retainlibswift_stdlib_core和,显然,没有内置的线程安全。

我们可以推测,@lazy稍后可能会引入其他关键字(类似于)。

15年7月20日更新:根据有关单例快速环境的此博文,可以使某些情况下的线程对您安全,即:

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

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

}

16年5月25日更新:关注快速发展的建议https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md-看起来像@atomic自己实现行为成为可能。


我用最近的一些信息更新了答案,希望对您
有所

1
嘿,感谢您提供的料斗拆卸器工具链接。看起来很整洁。
C0D3 '16


7

现在回答这个问题可能为时过早。目前swift缺少访问修饰符,因此没有明显的方法来添加代码来管理围绕属性getter / setter的并发性。此外,Swift语言似乎还没有有关并发的任何信息!(它也缺少KVO等...)

我认为这个问题的答案将在将来的版本中变得清晰。


回复:缺乏国际志愿者组织的,检查willSetdidSet-似乎是在路上的第一步
腰带Zats

1
willSet,didSet更适合那些总是需要自定义设置器的属性,因为它们必须做一些事情。例如,一个color属性需要在将其更改为其他值时重新绘制视图;现在,使用didSet可以轻松完成。
gnasher729 2014年

是的,这就是我“第一步”的意思:)我认为这可能是该功能可用但尚未完全实现的标志
Sash Zats 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上的示例项目会很好!
克拉斯

1
你好!这是完整样本。复制Atomic该类并使用Atomic().semaphoreSample()
Vasily Bodnarchuk

是的,我已经做到了。认为将它作为一个项目更新为最新语法会很好。使用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

这是我广泛使用的原子属性包装器。我将实际的锁定机制设置为协议,因此可以尝试使用不同的机制。我尝试了信号灯DispatchQueues,和pthread_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()
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.