sourcetip

오브젝티브-C의 "@synchronized"에 해당하는 스위프트는 무엇입니까?

fileupload 2023. 5. 23. 22:20
반응형

오브젝티브-C의 "@synchronized"에 해당하는 스위프트는 무엇입니까?

스위프트 책을 검색해봤는데 스위프트 버전의 @synchronized가 없습니다.Swift에서 상호 배제를 어떻게 합니까?

GCD를 사용할 수 있습니다.그것은 보다 조금 더 장황합니다.@synchronized대신 사용할 수 있습니다.

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}

저는 이것을 직접 찾다가 swift 내부에 이것을 위한 네이티브 구조가 아직 없다는 결론에 도달했습니다.

저는 이 작은 도우미 기능을 매트 브리지스와 다른 사람들에게서 본 몇몇 코드를 기반으로 만들었습니다.

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

사용법은 매우 간단합니다.

synced(self) {
    println("This is a synchronized closure")
}

이것과 관련하여 제가 발견한 한 가지 문제가 있습니다.배열을 lock 인수로 전달하면 이 시점에서 매우 둔감한 컴파일러 오류가 발생하는 것 같습니다.그렇지 않으면 원하는 대로 작동하는 것 같지만요.

Bitcast requires both operands to be pointer or neither
  %26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!

저는 여기서 많은 답을 좋아하고 사용하기 때문에 당신에게 가장 적합한 것을 선택하겠습니다.그렇긴 하지만, 목표-c와 같은 것이 필요할 때 내가 선호하는 방법.@synchronizedswift 2에 소개된 문을 사용합니다.

{ 
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    //
    // code of critical section goes here
    //

} // <-- lock released when this block is exited

방법의 이방의좋점중섹원한예션요방이하포는것나블입수니다있갈는다을록함로식으은)으로 포함된 을 나갈 수 입니다.return,break,continue,throw) 및 "deler 문 내의 문은 프로그램 제어가 어떻게 전송되는지에 관계없이 실행됩니다."1

다음 사이에 문을 끼워 넣을 수 있습니다.objc_sync_enter(obj: AnyObject?)그리고.objc_sync_exit(obj: AnyObject?)는 커버 에서 이러한 즉, @synchronized 서사이다니용합키방법을한러워에래아드는▁the▁is▁under다니합▁@사용▁methods▁those키.

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)

목표-C의 지시어 아날로그는 Swift에서 임의의 반환 유형과 양호한 동작을 가질 수 있습니다.

// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

문을 사용하면 임시 변수를 사용하지 않고 값을 직접 반환할 수 있습니다.


에 스위프트 2를 합니다.@noescape최적화를 를 속성으로 합니다.

// Swift 2
func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

GNewc [1] (내가 임의 반품 타입을 좋아하는 곳)와 Tod Cunningham [2] (내가 좋아하는 곳)의 답변을 기반으로 합니다.defer).

스위프트 4

Swift 4에서는 GCD 발송 대기열을 사용하여 리소스를 잠글 수 있습니다.

class MyObject {
    private var internalState: Int = 0
    private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default

    var state: Int {
        get {
            return internalQueue.sync { internalState }
        }

        set (newState) {
            internalQueue.sync { internalState = newState }
        }
    }
} 

최신 Swift 5에서는 반환 기능이 제공됩니다.

/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    return closure()
}

이를 사용하여 반환 값 기능을 활용합니다.

let returnedValue = synchronized(self) { 
     // Your code here
     return yourCode()
}

아니면 그렇지 않은 경우:

synchronized(self) { 
     // Your code here
    yourCode()
}

반환 기능을 추가하기 위해 다음을 수행할 수 있습니다.

func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
  objc_sync_enter(lockObj)
  var retVal: T = closure()
  objc_sync_exit(lockObj)
  return retVal
}

이후 다음을 사용하여 호출할 수 있습니다.

func importantMethod(...) -> Bool {
  return synchronize(self) {
    if(feelLikeReturningTrue) { return true }
    // do other things
    if(feelLikeReturningTrueNow) { return true }
    // more things
    return whatIFeelLike ? true : false
  }
}

Bryan McLemore 답변을 사용하여 Swift 2.0 지연 가능성으로 안전한 장원을 제공하는 객체를 지원하도록 확장했습니다.

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
    objc_sync_enter(lock)
    defer {
        objc_sync_exit(lock)
    }

    try block()
}

2018년 WWDC의 "충돌 및 충돌 로그 이해" 세션 414에서 동기화와 함께 DispatchQueues를 사용하여 다음과 같은 방법을 보여줍니다.

스위프트 4는 다음과 같아야 합니다.

class ImageCache {
    private let queue = DispatchQueue(label: "sync queue")
    private var storage: [String: UIImage] = [:]
    public subscript(key: String) -> UIImage? {
        get {
          return queue.sync {
            return storage[key]
          }
        }
        set {
          queue.sync {
            storage[key] = newValue
          }
        }
    }
}

어쨌든 장벽이 있는 동시 대기열을 사용하여 읽기 속도를 높일 수도 있습니다.동기화 및 비동기 읽기가 동시에 수행되고 새 값을 쓰는 것은 이전 작업이 완료될 때까지 기다립니다.

class ImageCache {
    private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
    private var storage: [String: UIImage] = [:]

    func get(_ key: String) -> UIImage? {
        return queue.sync { [weak self] in
            guard let self = self else { return nil }
            return self.storage[key]
        }
    }

    func set(_ image: UIImage, for key: String) {
        queue.async(flags: .barrier) { [weak self] in
            guard let self = self else { return }
            self.storage[key] = image
        }
    }
}

시도: NSRecursiveLock

교착 상태를 유발하지 않고 동일한 스레드에서 여러 번 획득할 수 있는 잠금입니다.

let lock = NSRecursiveLock()

func f() {
    lock.lock()
    //Your Code
    lock.unlock()
}

func f2() {
    lock.lock()
    defer {
        lock.unlock()
    }
    //Your Code
}

Objective-C 동기화 기능은 재귀 및 재귀 코드를 지원합니다.스레드는 단일 세마포어를 반복적으로 여러 번 사용할 수 있습니다. 다른 스레드는 스레드에서 얻은 모든 잠금을 해제할 때까지 사용할 수 없습니다. 즉, 모든 @synchronized() 블록이 정상적으로 또는 예외를 통해 종료됩니다.출처

스위프트 3

이 코드는 재입력 기능이 있으며 비동기 함수 호출과 함께 작동할 수 있습니다.이 코드에서는 일부 AsyncFunc()가 호출된 후 직렬 큐의 다른 함수 폐쇄가 처리되지만 signal()이 호출될 때까지 semaphore.wait()에 의해 차단됩니다.내부 대기열내가 틀리지 않았다면 동기화는 메인 스레드를 차단할 것이기 때문에 사용하면 안 됩니다.

let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)

internalQueue.async {

    self.semaphore.wait()

    // Critical section

    someAsyncFunc() {

        // Do some work here

        self.semaphore.signal()
    }
}

objc_sync_enter/objc_sync_message는 오류 처리가 없는 좋은 아이디어가 아닙니다.

Swift4에서 NSLock 사용:

let lock = NSLock()
lock.lock()
if isRunning == true {
        print("Service IS running ==> please wait")
        return
} else {
    print("Service not running")
}
isRunning = true
lock.unlock()

경고 NSLock 클래스는 POSIX 스레드를 사용하여 잠금 동작을 구현합니다.잠금 해제 메시지를 NSLock 개체로 보낼 때 메시지가 초기 잠금 메시지를 보낸 동일한 스레드에서 전송되었는지 확인해야 합니다.다른 스레드에서 잠금을 해제하면 정의되지 않은 동작이 발생할 수 있습니다.

Swift의 숙박 시설 포장지를 사용하면 다음과 같이 됩니다.

@propertyWrapper public struct NCCSerialized<Wrapped> {
    private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")

    private var _wrappedValue: Wrapped
    public var wrappedValue: Wrapped {
        get { queue.sync { _wrappedValue } }
        set { queue.sync { _wrappedValue = newValue } }
    }

    public init(wrappedValue: Wrapped) {
        self._wrappedValue = wrappedValue
    }
}

그러면 다음을 수행할 수 있습니다.

@NCCSerialized var foo: Int = 10

또는

@NCCSerialized var myData: [SomeStruct] = []

그런 다음 평소처럼 변수에 액세스합니다.

스위프트 동시성의 출현으로, 우리는 배우들을 사용할 것입니다.

작업을 사용하여 프로그램을 분리된 동시 조각으로 분할할 수 있습니다.태스크는 서로 격리되어 있기 때문에 동시에 실행하는 것이 안전하지만 태스크 간에 일부 정보를 공유해야 하는 경우도 있습니다.액터를 사용하면 동시 코드 간에 정보를 안전하게 공유할 수 있습니다.

클래스와 마찬가지로 액터도 참조 유형이므로 Class Are Reference Type(클래스는 참조 유형)의 값 유형과 참조 유형의 비교는 액터와 클래스에도 적용됩니다.클래스와 달리 행위자는 한 번에 한 작업만 가변 상태에 액세스할 수 있으므로 여러 작업의 코드가 행위자의 동일한 인스턴스와 상호 작용하는 것이 안전합니다.예를 들어 온도를 기록하는 배우는 다음과 같습니다.

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}

당신은 배우를 소개합니다.actor키워드 다음에 괄호 쌍으로 정의합니다.TemperatureLogger액터에는 액터 외부의 다른 코드가 액세스할 수 있는 속성이 있으며 액터 내부의 코드만 최대값을 업데이트할 수 있도록 최대 속성을 제한합니다.

자세한 내용은 WWDC 비디오 Swift 액터로 가변 상태 보호를 참조하십시오.


완전성을 위해 과거의 대안은 다음과 같습니다.

  • GCD 직렬 대기열:이것은 한 번에 하나의 스레드가 공유 리소스와 상호 작용하도록 보장하는 간단한 동시 이전 접근 방식입니다.

  • 동시 GCD 대기열이 있는 리더-라이터 패턴:읽기/쓰기 패턴에서 동시 발송 대기열을 사용하여 동기식이지만 동시에 읽기를 수행하지만(쓰기가 아닌 다른 읽기와만 동시에) 장벽을 통해 비동기식으로 쓰기를 수행합니다(해당 대기열의 다른 어떤 것과도 동시에 쓰기를 수행하지 않도록 강제함).이는 단순한 GCD 직렬 솔루션에 비해 성능 향상을 제공할 수 있지만, 실제로는 이점이 미미하며 추가 복잡성을 감수해야 합니다(예: 스레드 폭발 시나리오에 주의해야 함).IMHO, 저는 직렬 큐 패턴의 단순성을 고수하거나 성능 차이가 중요한 경우 완전히 다른 패턴을 사용하여 이러한 패턴을 피하는 경향이 있습니다.

  • 잠금: Swift 테스트에서 잠금 기반 동기화는 GCD 접근 방식보다 상당히 빠른 경향이 있습니다.잠금 장치는 몇 가지 맛이 있습니다.

    • NSLock상대적으로 효율적인 잠금 메커니즘입니다.
    • 성능이 가장 중요한 경우에는 "잠금 장치"를 사용하지만 Swift에서 사용할 때는 주의해야 합니다(https://stackoverflow.com/a/66525671/1271826) 참조).
    • 완전성을 위해 재귀적 잠금도 있습니다.임호 씨, 저는 심플한 게 좋겠어요.NSLock1파운드가 NSRecursiveLock반복 잠금은 남용될 수 있으며 종종 코드 냄새를 나타냅니다.
    • "스핀 잠금"에 대한 참조가 표시될 수 있습니다.수년 전에는 성능이 가장 중요한 곳에 고용되었지만, 현재는 불공정한 잠금 장치를 선호하여 사용되지 않습니다.
  • 기술적으로 동기화를 위해 세마포어를 사용할 수 있지만 모든 대안 중에서 가장 느린 경향이 있습니다.

여기에 제 벤치마크 결과의 개요를 설명합니다.

간단히 말해서, 요즘 저는 현대 코드베이스를 위한 배우, 비동기 대기 코드가 아닌 간단한 시나리오를 위한 GCD 직렬 큐, 그리고 성능이 필수적인 드문 경우에 잠금 장치를 사용합니다.

그리고 말할 필요도 없이, 우리는 종종 동기화의 수를 모두 줄이려고 노력합니다.가능하면 각 스레드가 자체 복사본을 얻는 값 유형을 사용하는 경우가 많습니다.동기화를 피할 수 없는 경우에는 가능한 한 동기화 수를 최소화하려고 합니다.

그림 이전 답변을 바탕으로 구축된 Swift 5 구현을 게시합니다.여러분 감사합니다!저는 값을 반환하는 것도 도움이 된다는 것을 알았기 때문에 두 가지 방법이 있습니다.

먼저 만드는 간단한 수업은 다음과 같습니다.

import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        closure()
    }
    public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        return closure()
    }
}

그런 다음 반환 값이 필요한 경우 다음과 같이 사용합니다.

return Sync.syncedReturn(self, closure: {
    // some code here
    return "hello world"
})

또는:

Sync.synced(self, closure: {
    // do some work synchronously
})

를 생성할 수 .Synchronised

은 다음예는을 사용한 입니다.NSLock할 수 있는 posix_locks e.t.cGCD, posix_locks e.t.c는 동기화에 사용할 수 .

@propertyWrapper public struct Synchronised<T> {
    private let lock = NSLock()

    private var _wrappedValue: T
    public var wrappedValue: T {
        get {
            lock.lock()
            defer {
                lock.unlock()
            }
            return _wrappedValue
        }
        set {
            lock.lock()
            defer {
                lock.unlock()
            }
            _wrappedValue = newValue
        }
    }

    public init(wrappedValue: T) {
        self._wrappedValue = wrappedValue
    }
}

@Synchronised var example: String = "testing"

@preckster 답변을 기반으로 합니다.

결론적으로, 여기에는 반환 값 또는 무효를 포함하는 더 일반적인 방법이 나와 있습니다.

import Foundation

extension NSObject {


    func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
    {
        objc_sync_enter(lockObj)
        defer {
            objc_sync_exit(lockObj)
        }

        return try closure()
    }


}

세부 사항

Xcode 8.3.1, Swift 3.1

작업

다른 스레드(비동기)에서 쓰기 값을 읽습니다.

코드

class AsyncObject<T>:CustomStringConvertible {
    private var _value: T
    public private(set) var dispatchQueueName: String
   
    let dispatchQueue: DispatchQueue
    
    init (value: T, dispatchQueueName: String) {
        _value = value
        self.dispatchQueueName = dispatchQueueName
        dispatchQueue = DispatchQueue(label: dispatchQueueName)
    }
    
    func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                _self._value = closure(_self._value)
            }
        }
    }
    
    func getValue(with closure: @escaping (_ currentValue: T)->() ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                closure(_self._value)
            }
        }
    }
    
    
    var value: T {
        get {
            return dispatchQueue.sync { _value }
        }
        
        set (newValue) {
            dispatchQueue.sync { _value = newValue }
        }
    }

    var description: String {
        return "\(_value)"
    }
}

사용.

print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)

print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
    let newValue = current*2
    print("previous: \(current), new: \(newValue)")
    return newValue
}

전체 샘플

확장 디스패치 그룹

extension DispatchGroup {
    
    class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
        let group = DispatchGroup()
        for index in 0...repeatNumber {
            group.enter()
            DispatchQueue.global(qos: .utility).async {
                action(index)
                group.leave()
            }
        }
        
        group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
            completion()
        }
    }
}

클래스 뷰 컨트롤러

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //sample1()
        sample2()
    }
    
    func sample1() {
        print("=================================================\nsample with variable")
        
        let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")
        
        DispatchGroup.loop(repeatNumber: 5, action: { index in
            obj.value = index
        }) {
            print("\(obj.value)")
        }
    }
    
    func sample2() {
        print("\n=================================================\nsample with array")
        let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
        DispatchGroup.loop(repeatNumber: 15, action: { index in
            arr.setValue{ (current) -> ([Int]) in
                var array = current
                array.append(index*index)
                print("index: \(index), value \(array[array.count-1])")
                return array
            }
        }) {
            print("\(arr.value)")
        }
    }
}

어때

final class SpinLock {
    private let lock = NSRecursiveLock()

    func sync<T>(action: () -> T) -> T {
        lock.lock()
        defer { lock.unlock() }
        return action()
    }
}

왜 자물쇠를 사용하는 것을 어렵고 번거롭게 합니까?발송 장벽을 사용합니다.

발송 장벽은 동시 대기열 내에 동기화 지점을 만듭니다.

실행 중에는 동시에 다른 코어를 사용할 수 있더라도 대기열의 다른 블록은 실행할 수 없습니다.

전용(쓰기) 잠금처럼 들린다면 그렇습니다.비배리어 블록은 공유(읽기) 잠금으로 간주할 수 있습니다.

대기열을 통해 리소스에 대한 모든 액세스가 수행되는 한, 장벽은 매우 저렴한 동기화를 제공합니다.

현재 스레드를 차단하지 않고 dispatch_sysc를 사용하는 것이 좋습니다.

dispatch_sysc(액세스)큐, {사전[개체].ID] = 개체 }

"eurobur"를 기반으로 하위 클래스 사례

class Foo: NSObject {
    func test() {
        print("1")
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
            print("3")
        }

        print("2")
    }
}


class Foo2: Foo {
    override func test() {
        super.test()

        print("11")
        objc_sync_enter(self)
        defer {
            print("33")
            objc_sync_exit(self)
        }

        print("22")
    }
}

let test = Foo2()
test.test()

출력:

1
2
3
11
22
33

또 다른 방법은 슈퍼 클래스를 만든 다음 상속하는 것입니다.이렇게 하면 GCD를 보다 직접적으로 사용할 수 있습니다.

class Lockable {
    let lockableQ:dispatch_queue_t

    init() {
        lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
    }

    func lock(closure: () -> ()) {
        dispatch_sync(lockableQ, closure)
    }
}


class Foo: Lockable {

    func boo() {
        lock {
            ....... do something
        }
    }

언급URL : https://stackoverflow.com/questions/24045895/what-is-the-swift-equivalent-to-objective-cs-synchronized

반응형