Swift与Objective-C的“ @synchronized”等效吗?


231

我已经搜索过Swift书,但是找不到@synchronized的Swift版本。如何在Swift中进行互斥?


1
我会使用调度屏障。屏障提供了非常便宜的同步。dispatch_barrier_async()。等
Frederick C. Lee

@ FrederickC.Lee,但是,如果您需要同步写入操作,例如为创建包装器,该removeFirst()怎么办?
ScottyBlades

Answers:


183

您可以使用GCD。它比更加冗长@synchronized,但可以代替:

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

12
这很棒,但是缺少@synchronized的重新输入功能。
Michael瀑布2014年

9
使用这种方法时,您需要小心。您的块可能在其他线程上执行。API文档说:“作为一种优化,此函数在可能的情况下在当前线程上调用该块。”
生物资料

20
马特·加拉格尔(Matt Gallagher)的精彩
wuf810

4
不,这会导致偶尔的死锁。
Tom Kraina

70
不,不,不。不错的尝试,但效果不理想。为什么?马特·加拉格尔(Matt Gallagher)的基础阅读(替代方法,注意事项的全面比较)和一个出色的实用程序框架,在这里:cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810提到了第一个(HT),但低估了这篇文章的质量。所有人都应该阅读。(请最小
投票

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

我发现了一个问题。此时,将数组作为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!

真好!请文件中查找错误,如果它仍然在1.0中的问题
MattD

14
这非常有用,并且@synchronized很好地保留了该块的语法,但是请注意,它与诸如@synchronizedObjective-C中的块之类的真实内置块语句并不相同,因为returnand break语句不再能像这样跳出周围的函数/循环如果这是一个普通的声明,那将是。
newacct 2014年

3
错误很可能是由于将数组作为值而不是引用传递的原因
james_alvarez,2015年

9
这可能是使用new defer关键字以确保objc_sync_exit即使closure引发也被调用的好地方。
devios1 2016年

3
@ t0rst根据链接到的文章将这个答案称为“有缺陷的”是无效的。文章说这种方法“比理想的慢一点”,并且“仅限于Apple平台”。但这并不能使其远距离“受损”。
RenniePet

150

我喜欢并在这里使用许多答案,因此,我将选择最适合您的一个。就是说,当我需要诸如Objective-C之类的东西时,我偏爱的方法@synchronized使用了deferSwift 2中引入的语句。

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

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

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

这个方法的好处是,你的关键部分可以退出所希望的任何方式包含块(例如returnbreakcontinuethrow),和“defer语句中的语句执行不管程序控制的传输方式。” 1个


我认为这可能是此处提供的最优雅的解决方案。感谢您的反馈意见。
Scott D

3
什么lock啊 如何lock初始化?
Van Du Tran

6
lock是任何Objective-C对象。
ɲeuroburɳ

1
优秀的!引入Swift 1时,我已经写了一些锁辅助方法,并且有一段时间没有再访问它们了。完全忘记了延期;这是要走的路!
兰迪

我喜欢这样,但是在Xcode 8中收到编译器错误“语句的语句块是未使用的闭包”。啊,我知道它们只是函数括号-太久找不到您的“ 1”引用链接了-谢谢!
邓肯·格罗内瓦尔德'16

83

您可以在objc_sync_enter(obj: AnyObject?)和之间夹入语句objc_sync_exit(obj: AnyObject?)。@synchronized关键字在幕后使用了这些方法。即

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

3
苹果会考虑使用私有API吗?
Drux

2
没有,objc_sync_enter并且objc_sync_exit在Objc-sync.h定义的方法,并开放源码:opensource.apple.com/source/objc4/objc4-371.2/runtime/...
bontoJR

如果多个线程尝试访问同一资源,第二个线程是否等待,重试或崩溃,会发生什么情况?
TruMan1 2016年

在@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

75

@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)的答案。


Xcode是告诉我,@noescape现在违约,并在斯威夫特3.已经过时
RenniePet

没错,此答案中的代码适用于Swift 2,并且需要对Swift 3进行一些修改。我将在有空的时候进行更新。
arediver

1
你能解释一下用法吗?也许有一个例子..预先感谢!就我而言,我有一个需要同步的Set,因为我在DispatchQueue中操纵了它的内容。
Sancho

@sancho我想保持这篇文章的简洁。您似乎在询问一般的并发编程准则,这是一个广泛的问题。尝试将其作为一个单独的问题提出!
潜水员

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可用。:/
特拉维斯·格里格斯

2
默认值为.serial
Duncan Groenewald,2016年

2
请注意,此模式无法正确防范大多数常见的多线程问题。例如,如果您要myObject.state = myObject.state + 1同时运行,它将不会计算总操作数,而是会产生不确定的值。为了解决该问题,应将调用代码包装在串行队列中,以使读取和写入都自动进行。当然,Obj-c @synchronised存在相同的问题,因此在这种意义上,您的实现是正确的。
Berik '18

1
是的,myObject.state += 1是读操作然后写操作的组合。其他一些线程仍然可以介于两者之间来设置/写入值。根据objc.io/blog/2018/12/18/atomic-variablesset在同步块/闭包中而不是在变量本身下运行会更容易。
Cyber​​Mew

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)来简化用法。
arediver

10

迅捷3

此代码具有重新输入功能,并且可以与异步函数调用一起使用。在此代码中,在调用someAsyncFunc()之后,将处理串行队列上的另一个函数关闭,但此操作将被semaphore.wait()阻止,直到调用signal()。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中,它们显示了使用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
        }
    }
}

您可能不需要使用同步来阻止读取并降低队列速度。您可以只使用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 wrap与其他解决方案(以及无队列)进行性能测试,但是还没有这样做。
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队列来同步访问,但这在原始问题中没有提到。而且只有并发队列才需要屏障-您可以简单地使用串行队列将相互排斥的块排队,以模拟锁。
法案

我的问题是,为什么要模仿锁?据我了解,由于开销大而不是队列中的障碍,因此不鼓励使用锁。
弗雷德里克·李

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

9
-1继承为您提供了子类型多态性,以增加耦合。如果不需要前者,请避免使用后者。别偷懒 首选组合,以便代码重用。
Jano
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.