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

다음은 내가 광범위하게 사용하는 원자 속성 래퍼입니다. 실제 잠금 메커니즘을 프로토콜로 만들어서 다른 메커니즘으로 실험 할 수있었습니다. 세마포어 DispatchQueues및 pthread_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.