Swift 변수는 원자 적입니까?


102

Objective-C에서는 원자 속성과 비 원자 속성을 구분합니다.

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

내 이해에 따르면 여러 스레드에서 원자로 정의 된 속성을 안전하게 읽고 쓸 수 있지만 동시에 여러 스레드에서 비 원자 속성 또는 ivar를 작성하고 액세스하면 잘못된 액세스 오류를 포함하여 정의되지 않은 동작이 발생할 수 있습니다.

따라서 Swift에 다음과 같은 변수가있는 경우 :

var object: NSObject

이 변수를 안전하게 읽고 쓸 수 있습니까? (이 작업의 실제 의미를 고려하지 않고).


앞으로는 @atomic또는 @nonatomic. 또는 기본적으로 원 자성입니다. (Swift는 너무 불완전합니다. 지금은 많이 알 수 없습니다.)
Bryan Chen

1
IMO, 그들은 기본적으로 모든 것을 비 원자로 만들 것이며 아마도 원자 재료를 만드는 특별한 기능을 제공 할 것입니다.
eonil

제쳐두고, atomic간단한 데이터 유형을 제외하고 일반적으로 속성과 스레드로부터 안전한 상호 작용에 충분하지 않은 것으로 간주됩니다. 객체의 경우 일반적으로 잠금 (예 : NSLock또는 @synchronized) 또는 GCD 큐 (예 : 직렬 큐 또는 "reader-writer"패턴을 사용하는 동시 큐)를 사용하여 스레드간에 액세스를 동기화합니다 .
Rob

@Rob, true, Objective-C (및 Swift)의 참조 계산으로 인해 원자 적 액세스없이 변수에 대한 동시 읽기 및 쓰기가 메모리 손상을 초래할 수 있습니다. 모든 변수에 원자 적 접근 권한이있는 경우 발생할 수있는 최악의 상황은 "논리적"경쟁 조건, 즉 예기치 않은 동작입니다.
lassej 2014-08-05

오해하지 마세요. Apple이 원자 적 행동 문제에 답하고 해결하기를 바랍니다. 단지 (a) atomic는 객체에 대한 스레드 안전성을 보장하지 않습니다. (b) 스레드 안전성을 보장하기 위해 앞서 언급 한 동기화 기술 중 하나를 적절하게 사용하는 경우 (무엇보다도 동시 읽기 / 쓰기 방지) 원자 적 문제는 논쟁의 여지가 있습니다. 그러나 우리 atomic는 실제 가치가 있는 단순한 데이터 유형을 위해 여전히 필요 / 원합니다 . 좋은 질문!
Rob

답변:


52

저수준 문서를 사용할 수 없기 때문에 가정하는 것은 매우 이르지만 어셈블리에서 공부할 수 있습니다. Hopper Disassembler 는 훌륭한 도구입니다.

@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_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
Hopper Disassembler 도구에 대한 링크를 주셔서 감사합니다. 깔끔해 보인다.
C0D3

11

Swift에는 스레드 안전성에 대한 언어 구조가 없습니다. 자체 스레드 안전 관리를 수행하기 위해 제공된 라이브러리를 사용한다고 가정합니다. pthread 뮤텍스, NSLock 및 뮤텍스 메커니즘으로 dispatch_sync를 포함하여 스레드 안전성을 구현하는 데 사용할 수있는 많은 옵션이 있습니다. 주제에 대한 Mike Ash의 최근 게시물을 참조하십시오. https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html 따라서 "Can 이 변수를 병렬로 안전하게 읽고 씁니까? " 아니오입니다.


7

이 질문에 답하기에는 이르다. 현재 swift에는 액세스 수정자가 없기 때문에 속성 getter / setter 주변의 동시성을 관리하는 코드를 추가하는 명확한 방법이 없습니다. 게다가 Swift Language는 아직 동시성에 대한 정보가없는 것 같습니다! (KVO 등도 부족합니다 ...)

이 질문에 대한 답은 향후 릴리스에서 분명해질 것이라고 생각합니다.


재 : KVO의 부족, 체크 아웃 willSet, didSet- 길에 첫 번째 단계가 될 것 같다
샤시 Zats

1
willSet, didSet은 무언가를해야했기 때문에 항상 사용자 지정 setter가 필요한 속성에 더 적합합니다. 예를 들어 속성이 다른 값으로 변경 될 때 뷰를 다시 그려야하는 색상 속성입니다. 이제 didSet을 사용하여 더 쉽게 수행 할 수 있습니다.
gnasher729

예, 그게 내가 "첫 단계"가 무엇을 의미하는 것이 : 나는 아직 사용할 수 있지만 완전히 구현되지되는 기능의 표시 될 수 가정
샤시 Zats

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()
바실리 Bodnarchuk

예, 이미했습니다. 최신 구문으로 업데이트되는 프로젝트로 사용하는 것이 좋을 것이라고 생각했습니다. Swift를 사용하면 구문이 항상 변경됩니다. 그리고 당신의 대답은 : 지금까지 가장 최근의 것입니다
클라스

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_t. 은 pthread_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.