스위프트는 Objective-C의“@synchronized”에 해당하는 것은 무엇입니까?


231

Swift 책을 검색했지만 @synchronized의 Swift 버전을 찾을 수 없습니다. Swift에서 상호 배제를 어떻게합니까?


1
파견 장벽을 사용합니다. 배리어는 매우 저렴한 동기화를 제공합니다. dispatch_barrier_async (). 등
프레 더릭 C. 리

@ FrederickC.Lee, 래퍼를 만들 때와 같이 쓰기 를 동기화 하려면 어떻게해야 removeFirst()합니까?
ScottyBlades 2016 년

답변:


183

GCD를 사용할 수 있습니다. 보다 약간 장황 @synchronized하지만 대체 기능으로 작동합니다.

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

12
이것은 훌륭하지만 @synchronized로 재 입력 할 수있는 능력이 부족합니다.
Michael Waterfall

9
이 접근 방식을 사용하면 조심해야합니다. 다른 스레드에서 블록이 실행될 수 있습니다. API 문서에 따르면 : "최적화로서이 함수는 가능한 경우 현재 스레드에서 블록을 호출합니다."
bio

20

4
아니, 가끔 교착 상태가 발생합니다.
Tom Kraina

70
아냐 아냐 좋은 시도이지만 완벽하게 작동합니다. 왜? Matt Gallagher의 필수 자료 (대안,주의 사항에 대한 포괄적 인 비교) 및 훌륭한 유틸리티 프레임 워크 : cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810이 처음 언급 한 (HT) 이 기사가 얼마나 좋은지 과소 평가했다. 모두 읽어야합니다. (더 이상은 초기에 볼 수 있도록하기위한 최소하여이 작업을 upvote에, 그러나하시기 바랍니다.)
t0rst

181

나는 이것을 직접 찾고 있었고 아직 이것에 대한 신속한 내부에 네이티브 구성이 없다는 결론에 도달했습니다.

Matt Bridges 및 다른 코드에서 본 코드 중 일부를 기반 으로이 작은 도우미 기능을 구성했습니다.

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

사용법은 매우 간단합니다

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

내가 찾은 문제가 하나 있습니다. 잠금 인수로 배열을 전달하면이 시점에서 매우 혼란스러운 컴파일러 오류가 발생하는 것으로 보입니다. 그렇지 않으면 원하는대로 작동하는 것 같습니다.

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!

좋은! 1.0에서 여전히 문제가되는 경우 버그를 제기하십시오.
MattD

14
이것은 매우 유용하고 구문 보존 @synchronized좋게 블록을하지만, 참고 그것은 같은 실제 내장 블록 문 동일하지 않은 것을 @synchronized때문에, 목표 - C의 블록 returnbreak같은 주변 기능 / 루프 밖으로 뛰어 더 이상 작동 문 이것이 평범한 진술이라면 가능할 것입니다.
newacct

3
그 배열은 값없는 참조로 전달에 오류 가능성 때문이다
james_alvarez

9
던질 때에도 호출 defer을 보장하기 위해 새 키워드를 사용하는 것이 좋습니다 . objc_sync_exitclosure
devios1

3
@ t0rst 링크 된 기사에 근거하여이 답변을 "flawed"라고 부르는 것은 유효하지 않습니다. 이 기사는이 방법이 "이상적인 것보다 약간 느리며" "애플 플랫폼으로 제한된다"고 밝혔다. 그렇다고해서 오랫동안 "결점"되지는 않습니다.
RenniePet

150

나는 여기에 많은 답변을 좋아하고 사용하므로 가장 적합한 것을 선택하십시오. 즉, objective-c와 같은 것이 필요할 때 선호하는 방법 은 신속한 2에 도입 된 진술을 @synchronized사용합니다 defer.

{ 
    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), 그리고 "를 연기 문 내의 문은 프로그램 제어가 전송되는 방식에 상관없이 실행됩니다." 1


나는 이것이 아마도 여기에 제공된 가장 우아한 해결책이라고 생각합니다. 의견을 보내 주셔서 감사합니다.
Scott D

3
무엇입니까 lock? lock초기화 방법
Van Du Tran

6
lockobjective-c 객체입니다.
ɲ euroburɳ

1
우수한! Swift 1이 소개되었을 때 몇 가지 잠금 도우미 메서드를 작성했으며 잠시 동안 다시 방문하지 않았습니다. 연기를 완전히 잊었다. 이것은 갈 길입니다!
랜디

나는 이것을 좋아하지만 Xcode 8에서 컴파일러 에러 "Braced 문장 블록은 사용되지 않은 클로저이다"라는 오류를 얻는다.
던컨 그로 네 발트

83

objc_sync_enter(obj: AnyObject?)와 사이에 문을 삽입 할 수 있습니다 objc_sync_exit(obj: AnyObject?). @synchronized 키워드는 표지 아래 해당 방법을 사용하고 있습니다. 즉

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

3
이것이 Apple의 개인 API 사용으로 간주됩니까?
Drux

2
아니, objc_sync_enterobjc_sync_exitObjc - sync.h에 정의 된 방법이며, 오픈 소스 : opensource.apple.com/source/objc4/objc4-371.2/runtime/...
bontoJR

여러 스레드가 동일한 리소스에 액세스하려고하면 두 번째 스레드가 대기, 재시도 또는 충돌합니까?
TruMan1

@bontoJR는 말에 추가, objc_sync_enter(…)& objc_sync_exit(…)등 아이폰 OS / 맥 OS / 기관이 제공 한 공개 헤더이다. API ( ….sdk경로 안에있는 것처럼 보입니다 usr/include/objc/objc-sync.h) . 공개 API인지 여부를 확인하는 가장 쉬운 방법은 (Xcode에서) 함수 이름을 입력하는 것입니다 (예 : objc_sync_enter()C 함수에 인수를 지정할 필요가 없음) . 해당 API의 헤더 파일 이 표시되면 공개입니다 (헤더가 공개되지 않은 경우 헤더를 볼 수 없으므로) .
슬립 D. 톰슨

75

@synchronizedObjective-C 의 지시문 아날로그는 rethrowsSwift에서 임의의 반환 유형과 훌륭한 동작을 가질 수 있습니다 .

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

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


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


엑스 코드는 @noescape는 기본적 것을 말해되고 스위프트 3에서 더 이상 사용되지 않습니다
RenniePet

맞습니다.이 답변의 코드는 Swift 2 용이며 Swift 3에 맞게 조정해야합니다. 시간이되면 업데이트하겠습니다.
wasdiver

1
사용법을 설명 할 수 있습니까? 예를 들어 .. 미리 감사드립니다! 필자의 경우 DispatchQueue의 내용을 조작하기 때문에 동기화 해야하는 세트가 있습니다.
sancho 2016 년

@sancho이 게시물을 간결하게 유지하고 싶습니다. 일반적인 동시 프로그래밍 지침에 대해 묻는 것 같습니다. 그것은 광범위한 질문입니다. 별도의 질문으로 요청하십시오!
wasdiver

41

스위프트 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 }
        }
    }
} 

이것은 XCode8.1에서 작동하지 않는 것 같습니다. .serial사용할 수없는 것 같습니다. 그러나 .concurrent가능합니다. : /
Travis Griggs

2
기본값은 .serial
Duncan Groenewald

2
이 패턴은 대부분의 일반적인 멀티 스레드 문제를 제대로 방지하지 못합니다. 예를 들어, myObject.state = myObject.state + 1동시에 실행 하는 경우 총 작업 수를 계산하지 않고 비 결정적 값을 산출합니다. 이 문제를 해결하려면 읽기 및 쓰기가 원자 적으로 발생하도록 호출 코드를 직렬 큐에 래핑해야합니다. 물론 Obj-c도 @synchronised같은 문제가 있으므로 그런 의미에서 구현이 정확합니다.
Berik

1
예, myObject.state += 1읽기와 쓰기 작업의 조합입니다. 다른 스레드는 값을 설정 / 쓰기 위해 여전히 사용될 수 있습니다. 당으로 objc.io/blog/2018/12/18/atomic-variables ,을 실행하는 것이 더 쉽습니다 set변수 자체에서 대신이 아닌 동기 블록 / 폐쇄에.
CyberMew

23

반환 기능을 추가하려면 다음을 수행하십시오.

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

23

Bryan McLemore의 답변을 사용하여 Swift 2.0 지연 기능으로 안전한 영지에 던지는 객체를 지원하도록 확장했습니다.

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

    try block()
}

내 답변 에서 볼 수 있듯이 rethrows던지지 않는 클로저 (사용하지 않아도 됨 try) 로 사용 을 단순화 하는 것이 좋습니다 .
wasdiver

10

스위프트 3

이 코드는 재 입력 기능이 있으며 비동기 함수 호출과 함께 작동 할 수 있습니다. 이 코드에서 someAsyncFunc ()가 호출 된 후 직렬 큐의 다른 함수 클로저는 처리되지만 signal ()이 호출 될 때까지 semaphore.wait ()에 의해 차단됩니다. 실수하지 않으면 기본 스레드를 차단하므로 internalQueue.sync를 사용하면 안됩니다.

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_exit는 오류 처리 없이는 좋은 생각이 아닙니다.


어떤 오류 처리? 컴파일러는 던지는 것을 허용하지 않습니다. 반면에 objc_sync_enter / exit를 사용하지 않으면 성능이 크게 떨어집니다.
gnasher729

8

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

신속한 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
        }
    }
}

동기화를 사용하여 읽기를 차단하고 대기열을 느리게 할 필요는 없습니다. 직렬 쓰기에는 sync를 사용할 수 있습니다.
Basheer_CAD

6

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 객체로 보낼 때 초기 잠금 메시지를 보낸 동일한 스레드에서 메시지가 보내 졌는지 확인해야합니다. 다른 스레드에서 잠금을 잠금 해제하면 동작이 정의되지 않을 수 있습니다.



6

최신 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()
}

2
이것은 정답이며 인정되고 고도로 찬성 된 답변이 아닙니다 (에 따라 다름 GCD). 본질적으로 아무도 사용 방법을 사용하거나 이해하지 못하는 것 같습니다 Thread. 나는 그것에 행복하다-반면 GCD에 어려움과 한계로 가득하다.
javadba

4

시도 : NSRecursiveLock

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

let lock = NSRecursiveLock()

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

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

2

그림 이전 답변으로 작성된 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
})

try public class func synced<T>(_ lock: Any, closure: () -> T)는 void 및 다른 유형 모두에서 작동합니다. 재성장 물건도 있습니다.
hnh

@hnh 당신은 재성장 물건 무엇을 의미합니까? 또한 <T> 유형의 일반 메소드에 대한 예제 호출을 기꺼이 공유하면 답변을 업데이트하는 데 도움이됩니다.
TheJeff

다시 자라지 않고 다시 자라다, srz
hnh

1

세부

xCode 8.3.1, 신속한 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
}

전체 샘플

확장 DispatchGroup

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()
        }
    }
}

ViewController 클래스

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)")
        }
    }
}

1

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] = []

그런 다음 평소처럼 변수에 액세스하십시오.


1
나는이 솔루션을 좋아하지만 @Decorating 사람들의 비용에 대해 궁금했다. 왜냐하면 그렇게 DispatchQueue하면 사용자에게 숨겨진 것을 만드는 부작용 이 있습니다. 이 SO 참조가 마음을 편안하게하는 것을 발견했습니다. stackoverflow.com/a/35022486/1060314
Adam Venturella

속성 래퍼 자체는 아주 가볍습니다. 구조체이기 때문에 만들 수있는 가장 가벼운 것 중 하나입니다. DispatchQueue의 링크에 감사드립니다. queue.sync 랩에서 다른 솔루션 (및 대기열 없음)에서 성능 테스트를 수행해야했지만 그렇게하지 않았습니다.
drewster

1

결론적으로, 여기에는 반환 값 또는 void를 포함하고 던지는 일반적인 방법이 있습니다.

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()
    }


}

0

왜 어렵고 자물쇠가 번거 롭습니까? 디스패치 배리어를 사용하십시오.

디스패치 장벽은 동시 큐 내에 동기화 지점을 작성합니다.

실행 중일 때 동시에 다른 코어를 사용할 수 있어도 큐의 다른 블록을 실행할 수 없습니다.

그것이 독점적 인 (쓰기) 잠금처럼 들린다면 그것은 그렇습니다. 비 배리어 블록은 공유 (읽기) 잠금으로 생각할 수 있습니다.

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


2
내 말은, 액세스를 동기화하기 위해 GCD 큐를 사용한다고 가정하지만 원래 질문에서는 언급하지 않았습니다. 장벽은 동시 대기열에만 필요합니다. 직렬 대기열을 사용하여 상호 배제 된 블록을 대기열에 넣어 잠금을 에뮬레이트 할 수 있습니다.
Bill

내 질문, 왜 자물쇠를 모방합니까? 내가 읽은 내용에서 오버 헤드 대 큐 내의 장벽으로 인해 잠금이 권장되지 않습니다.
Frederick C. Lee

0

유로 버 (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

0

dispatch_barrier_async가 더 나은 방법이지만 현재 스레드를 차단하지는 않습니다.

dispatch_barrier_async (accessQueue, {dictionary [object.ID] = 객체})


-5

또 다른 방법은 수퍼 클래스를 만든 다음 상속하는 것입니다. 이런 식으로 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
        }
    }

10
-1 상속은 커플 링 증가에 대한 대가로 하위 유형 다형성을 제공합니다. 전자가 필요하지 않으면 나중에 피하십시오. 게으르지 마십시오. 코드 재사용을위한 구성을 선호하십시오.
Jano
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.