programing

스위프트 변수가 원자인가요?

minimums 2023. 10. 10. 20:12
반응형

스위프트 변수가 원자인가요?

Objective-C에서는 원자와 비원자의 특성을 구분할 수 있습니다.

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

여러 스레드에서 원자로 정의된 속성을 안전하게 읽고 쓸 수 있는 반면, 여러 스레드에서 원자로 정의되지 않은 속성이나 아이바를 동시에 쓰고 액세스하면 잘못된 액세스 오류를 포함하여 정의되지 않은 동작이 발생할 수 있습니다.

Swift에 이와 같은 변수가 있는 경우:

var object: NSObject

제가 이 변수를 안전하게 병렬로 읽고 쓸 수 있습니까? (이것을 하는 것의 실제 의미는 고려하지 않고)

낮은 수준의 문서가 없기 때문에 가정하기는 매우 이르지만, 조립부터 공부할 수 있습니다.호퍼 분해기는 훌륭한 도구입니다.

@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스스로 실행하는 행동입니다.

스위프트는 실 안전에 관한 언어 구성이 없습니다.제공된 라이브러리를 사용하여 자체 스레드 안전 관리를 수행한다고 가정합니다.pthread mutex, NSLock 및 dispatch_sync를 뮤텍스 메커니즘으로 사용하는 등 스레드 안전을 구현하는 데 많은 옵션이 있습니다.이 주제에 대한 Mike Ash의 최근 게시물 참조: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html 그렇다면 "이 변수를 안전하게 병렬로 읽고 쓸 수 있습니까?"라는 질문에 대한 직접적인 대답은 아니오입니다.

이 질문에 대답하기에는 아마도 이릅니다.현재 swift는 접근 수정자가 부족하여 속성 getter/setter 주변의 동시성을 관리하는 코드를 추가할 명확한 방법이 없습니다.게다가 스위프트 랭귀지는 아직 동시성에 대한 정보가 없는 것 같습니다! (KVO 등도 부족합니다.

이 질문에 대한 답은 앞으로 공개될 때 명확해질 것이라고 생각합니다.

세부 사항

  • Xcode 9.1, 스위프트 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()

결과

enter image description here

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

여기 제가 많이 사용하는 원자성 물품 포장지가 있습니다.실제 잠금 메커니즘을 프로토콜로 만들어 다양한 메커니즘으로 실험할 수 있었습니다.데,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()
    }
}

언급URL : https://stackoverflow.com/questions/24157834/are-swift-variables-atomic

반응형