Objective-Cの「@synchronized」に相当するSwiftは何ですか?


231

Swiftブックを検索しましたが、@ synchronizedのSwiftバージョンが見つかりません。Swiftで相互排除を行うにはどうすればよいですか?


1
派遣バリアを使用します。バリアは非常に安価な同期を提供します。dispatch_barrier_async()。等
フレデリックC.リー

@ FrederickC.Lee、ただし、ラッパーを作成するときなど、書き込みを同期する必要がある場合はどうなりますremoveFirst()か?
ScottyBlades

回答:


183

GCDを使用できます。は少し冗長です@synchronizedが、代わりに機能します。

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

12
これはすばらしいことですが、@ synchronizedを使用した場合の再入力機能はありません。
マイケルの滝

9
このアプローチでは注意が必要です。ブロックが他のスレッドで実行される可能性があります。APIドキュメントでは、「最適化として、この関数は可能な場合に現在のスレッドのブロックを呼び出します」と述べています。
バイオ

20
これについてのマットギャラガーのすばらしい記事:cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html
wuf810

4
いいえ、これは時々デッドロックを引き起こします。
Tom Kraina、2016年

70
いやいやいや。うまくいきましたが、うまく機能しません。どうして?ここでは必読(代替案の総合的な比較、注意)とマット・ギャラガーから大きな有用性フレームワーク、:cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810は、この最初の(HT)を述べたが、この記事がどれほど優れているかを軽視しました。すべて読む必要があります。(最初は表示されるようにするため、これを最小限に
投票して

181

私はこれを自分で探していましたが、Swiftの内部にはまだネイティブコンストラクトがないという結論に達しました。

Matt Bridgesや他の人から見たコードの一部に基づいて、この小さなヘルパー関数を作成しました。

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

使い方はかなり簡単です

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

これに関して私が見つけた問題が1つあります。この時点で、ロック引数として配列を渡すと、非常にわかりにくいコンパイラエラーが発生するようです。それ以外の場合は希望どおりに機能するようですが。

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の問題である場合は、バグを
報告してください

14
これはかなり便利ですとの構文維持し@synchronized、うまくブロックしますが、ノートでは、それはのような本当の組み込みブロック文と同じではないこと@synchronizedから、Objective-Cの中のブロックreturnbreak同様、周囲の機能/蚊帳の外にジャンプすることはもはや仕事ステートメントこれが普通の発言だったら。
newacct 2014年

3
エラーは、配列が参照ではなく値として渡されることが原因である可能性があります
james_alvarez

9
これはおそらく、スローした場合でもdefer確実objc_sync_exitに呼び出されるように新しいキーワードを使用するのに最適な場所closureです。
devios1

3
@ t0rstリンク先の記事に基づいてこの回答を「欠陥」と呼ぶことは無効です。この記事は、この方法は「理想よりも少し遅い」、「Appleプラットフォームに限定されている」と述べています。だからといって、それをロングショットで「欠陥がある」わけではありません。
RenniePet 16

151

ここでは多くの回答を気に入って使用しているので、あなたに最適なものを選択します。そうは言っても、objective-cのようなものが必要なときに私が好む方法は、swift 2で導入され@synchronizeddeferステートメントを使用しています。

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

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

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

この方法の良いところは、あなたのクリティカルセクションは、どんなファッションに含むブロックを終了することができるということです(例えば、所望のreturnbreakcontinuethrow)、および「延期ステートメント内のステートメントは、プログラム制御が転送されたかに関係なく実行されます。」1


これはおそらく、ここで提供される最もエレガントなソリューションだと思います。ご意見をいただきありがとうございます。
Scott D

3
なにlock?どのようにlock初期化されますか?
Van Du Tran

6
lock任意のobjective-cオブジェクトです。
ɲeuroburɳ

1
優れた!Swift 1が導入されたとき、私はいくつかのロックヘルパーメソッドを作成していましたが、しばらくの間これらを再訪していませんでした。延期を完全に忘れていました。これは進むべき道です!
ランディ

私はこれが好きですが、Xcode 8で「エラーのステートメントのブレースブロックは未使用のクロージャーです」というコンパイラエラーが発生します。
Duncan Groenewald 2016年

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、2015年

2
いいえ、objc_sync_enterobjc_sync_exitにObjC-sync.hで定義された方法であり、オープンソースです:opensource.apple.com/source/objc4/objc4-371.2/runtime/...
bontoJR

複数のスレッドが同じリソースにアクセスしようとした場合、2番目のスレッドは待機、再試行、またはクラッシュしますか?
TruMan1

@bontoJRの発言に加えて、objc_sync_enter(…)objc_sync_exit(…)はiOS / macOS / etcによって提供されるパブリックヘッダーです。API ….sdkパスの内側にあるように見えますusr/include/objc/objc-sync.h。何かがパブリックAPIであるかどうかを確認する最も簡単な方法は、(Xcodeで)関数名を入力することです(例:objc_sync_enter()C関数の場合、引数を指定する必要はありません)。その後、コマンドクリックしてみます。そのAPIのヘッダーファイルが表示されている場合は、問題ありません(ヘッダーが公開されていないとヘッダーを表示できないため)
Slipp D. Thompson 2018

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


Xcodeは、@ noescapeがデフォルトになり、Swift 3で廃止されることを教えてくれます
RenniePet

そうです、この回答のコードはSwift 2用であり、Swift 3にいくらかの適応が必要です。時間があるときに更新します。
ワーファーダイバー、

1
使い方を説明してもらえますか?多分例で..事前に感謝します!私の場合、DispatchQueueでコンテンツを操作しているため、同期する必要のあるセットがあります。
sancho 2017年

@sanchoこの投稿を簡潔にしたいと思います。一般的な並行プログラミングのガイドラインについて質問しているようですが、それは幅広い質問です。別の質問として質問してください。
ワリダイバー2017

41

SWIFT 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、利用可能です。:/
トラビスグリッグス2016年

2
デフォルトは.serialです
Duncan Groenewald 2016年

2
このパターンは、最も一般的なマルチスレッドの問題から適切に保護されないことに注意してください。たとえば、myObject.state = myObject.state + 1同時に実行した場合、操作の総数はカウントされず、代わりに非決定的な値が生成されます。この問題を解決するには、呼び出しコードをシリアルキューにラップして、読み取りと書き込みの両方がアトミックに行われるようにする必要があります。もちろん、Obj-c @synchronisedにも同じ問題があります。その意味で、実装は正しいです。
Berik

1
はい、myObject.state += 1読み取り操作と書き込み操作の組み合わせです。他のいくつかのスレッドは、値の設定/書き込みの間にまだ入ることができます。あたりとしてobjc.io/blog/2018/12/18/atomic-variables、実行するために容易になるset代わりにしていない変数自体の下のシンクブロック/閉鎖に。
Cyber​​Mew

24

ブライアンマクレモアの回答を使用して、Swift 2.0の据え置き機能で安全な邸宅を投入するオブジェクトをサポートするように拡張しました。

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

    try block()
}

私の答えに示されいるようにrethrowsスローしないクロージャーでの使用を単純化するために使用する方が良いでしょう(使用する必要はありませんtry)。
2015

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

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 2017

8

2018 WWDCの「クラッシュとクラッシュログについて」セッション414では、DispatchQueuesと同期を使用して次の方法を示しています。

Swiftでは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 2018

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。私はvに満足してい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の実装を投稿します。みんなありがとう!値を返すものもあると便利なので、2つの方法を用意しました。

以下は、最初に作成する単純なクラスです。

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

試してみてくださいpublic class func synced<T>(_ lock: Any, closure: () -> T)。voidと他のタイプの両方で機能します。再成長するものもあります。
HNH

@hnh再生再生のものとはどういう意味ですか?また、答えを更新するのに役立つ<T>型のジェネリックメソッドの呼び出し例を共有したい場合は、それをどこで行うかが好きです。
TheJeff

再スローではなく、再成長、SRZ
HNH

1

細部

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
}

完全なサンプル

拡張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リファレンスを見つけて、心を
安らげました

プロパティラッパー自体は非常に軽量です-単なる構造体なので、作成できる最も軽いものの1つです。DispatchQueueのリンクをありがとうございます。私は心の奥で、queue.syncラップと他のソリューション(およびキューなし)でいくつかのパフォーマンステストを実行する必要がありましたが、実行していませんでした。
ドリュースター

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キューを使用してアクセスを同期することを想定していますが、それは元の質問では言及されていません。また、バリアが必要なのは並行キューの場合のみです。シリアルキューを使用して、相互に除外されたブロックをキューに入れ、ロックをエミュレートできます。
ビル・

私の質問、なぜロックをエミュレートするのですか?私が読んだものから、オーバーヘッドはキュー内のバリアと比較して、ロックは推奨されません。
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] = object})


-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、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.