Swiftブックを検索しましたが、@ synchronizedのSwiftバージョンが見つかりません。Swiftで相互排除を行うにはどうすればよいですか?
removeFirst()
か?
Swiftブックを検索しましたが、@ synchronizedのSwiftバージョンが見つかりません。Swiftで相互排除を行うにはどうすればよいですか?
removeFirst()
か?
回答:
GCDを使用できます。は少し冗長です@synchronized
が、代わりに機能します。
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
私はこれを自分で探していましたが、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!
@synchronized
、うまくブロックしますが、ノートでは、それはのような本当の組み込みブロック文と同じではないこと@synchronized
から、Objective-Cの中のブロックreturn
とbreak
同様、周囲の機能/蚊帳の外にジャンプすることはもはや仕事ステートメントこれが普通の発言だったら。
defer
確実objc_sync_exit
に呼び出されるように新しいキーワードを使用するのに最適な場所closure
です。
ここでは多くの回答を気に入って使用しているので、あなたに最適なものを選択します。そうは言っても、objective-cのようなものが必要なときに私が好む方法は、swift 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
lock
?どのようにlock
初期化されますか?
lock
任意のobjective-cオブジェクトです。
objc_sync_enter(obj: AnyObject?)
との間でステートメントを挟むことができobjc_sync_exit(obj: AnyObject?)
ます。@synchronizedキーワードは、これらのメソッドを内部で使用しています。すなわち
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
とobjc_sync_exit
にObjC-sync.hで定義された方法であり、オープンソースです:opensource.apple.com/source/objc4/objc4-371.2/runtime/...
objc_sync_enter(…)
&objc_sync_exit(…)
はiOS / macOS / etcによって提供されるパブリックヘッダーです。API (….sdk
パスの内側にあるように見えますusr/include/objc/objc-sync.h
)。何かがパブリックAPIであるかどうかを確認する最も簡単な方法は、(Xcodeで)関数名を入力することです(例:objc_sync_enter()
C関数の場合、引数を指定する必要はありません)。その後、コマンドクリックしてみます。そのAPIのヘッダーファイルが表示されている場合は、問題ありません(ヘッダーが公開されていないとヘッダーを表示できないため)。
@synchronized
Objective-Cのディレクティブのアナログは、rethrows
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()
}
この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
。
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 }
}
}
}
.serial
利用できないようです。しかし.concurrent
、利用可能です。:/
myObject.state = myObject.state + 1
同時に実行した場合、操作の総数はカウントされず、代わりに非決定的な値が生成されます。この問題を解決するには、呼び出しコードをシリアルキューにラップして、読み取りと書き込みの両方がアトミックに行われるようにする必要があります。もちろん、Obj-c @synchronised
にも同じ問題があります。その意味で、実装は正しいです。
myObject.state += 1
読み取り操作と書き込み操作の組み合わせです。他のいくつかのスレッドは、値の設定/書き込みの間にまだ入ることができます。あたりとしてobjc.io/blog/2018/12/18/atomic-variables、実行するために容易になるset
代わりにしていない変数自体の下のシンクブロック/閉鎖に。
ブライアンマクレモアの回答を使用して、Swift 2.0の据え置き機能で安全な邸宅を投入するオブジェクトをサポートするように拡張しました。
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
リターン機能を追加するには、次のようにします。
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
}
}
スウィフト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は、エラー処理なしではお勧めできません。
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
}
}
}
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 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()
}
GCD
)。それは本質的に思える誰も用途をや使い方を理解していますThread
。私はvに満足していGCD
ます。一方、落とし穴や制限に満ちています。
試してください:NSRecursiveLock
デッドロックを引き起こさずに、同じスレッドによって複数回取得できるロック。
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
図以前の回答に基づいて作成した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と他のタイプの両方で機能します。再成長するものもあります。
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)")
}
}
}
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] = []
次に、通常どおりに変数にアクセスします。
DispatchQueue
。ユーザーから隠されたを作成するという副作用があるためです。私はこのSOリファレンスを見つけて、心を
なぜそれを難しくし、ロックの手間をかけるのですか?ディスパッチバリアを使用します。
ディスパッチバリアは、同時キュー内に同期ポイントを作成します。
実行中は、同時実行で他のコアが使用可能であっても、キューの他のブロックは実行できません。
それが排他的(書き込み)ロックのように聞こえる場合は、そうです。非バリアブロックは、共有(読み取り)ロックと考えることができます。
リソースへのすべてのアクセスがキューを介して実行される限り、バリアは非常に安価な同期を提供します。
ɲ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
}
}