什么是Swift等同于Objective-C的"@synchronized"?

Bil*_*ill 217 concurrency mutex swift

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

con*_*gan 174

使用GCD.它比一个更冗长@synchronized,但作为替代品非常好用:

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}
Run Code Online (Sandbox Code Playgroud)

  • 不,不,不.不错的尝试,但工作不完美.为什么?基本阅读(替代方案,注意事项的综合比较)和Matt Gallagher的一个很好的实用框架,在这里:https://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810提到了这一点首先(HT),但低估了这篇文章有多好.所有人都应该读.(请在最低限度上进行投票,以使其最初可见,但不能再显示.) (60认同)
  • 来自Matt Gallagher的伟大文章:http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html (19认同)
  • 这很棒,但缺少@synchronized的重新输入功能. (8认同)
  • 有了这种方法,你需要小心.您的块可能会在其他某个线程上执行.API文档说:"作为优化,此函数在可能的情况下调用当前线程上的块." (7认同)
  • 有人可以澄清为什么这个答案可能会导致僵局吗?Matt Gallagher 的文章清楚地说明了为什么这会比“@synchronized”慢,但为什么会导致死锁?@TomKraina @bio @t0rst (5认同)
  • 不,这会导致偶尔的死锁. (3认同)

Bry*_*ore 171

我自己一直在寻找这个,并得出结论,这里还没有swift内部的原生构造.

我根据我从Matt Bridges和其他人那里看到的一些代码构建了这个小帮助函数.

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}
Run Code Online (Sandbox Code Playgroud)

用法很简单

synced(self) {
    println("This is a synchronized closure")
}
Run Code Online (Sandbox Code Playgroud)

我发现有一个问题.传递一个数组作为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!
Run Code Online (Sandbox Code Playgroud)

  • 这非常有用并且很好地保留了`@ synchronized`块的语法,但请注意它与Objective-C中的`@ synchronized`块之类的真实内置块语句不同,因为`return`和`break`如果这是一个普通的语句,语句不再像跳出周围的函数/循环一样工作. (13认同)
  • 这可能是使用新的`defer`关键字以确保即使`closure`抛出也会调用`objc_sync_exit`的好地方. (8认同)
  • 错误可能是由于数组作为值而不是引用传递 (3认同)
  • @ t0rst根据链接到的文章将这个答案称为“有缺陷的”是无效的。文章说,这种方法“比理想方法慢一点”,并且“仅限于Apple平台”。但这并不能使其远距离“受损”。 (3认同)
  • 这篇非常有趣的文章解释了 `objc_sync_xxx` 的一个陷阱:https://straypixels.net/swift-dictionary-locking/ (2认同)

ɲeu*_*urɳ 145

我喜欢并使用这里的许多答案,所以我会选择最适合你的方法.也就是说,当我需要像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
Run Code Online (Sandbox Code Playgroud)

这个方法的好处是,你的关键部分可以退出所希望的任何方式包含块(例如return,break,continue,throw),和"defer语句中的语句,无论程序控制的传输方式执行." 1

  • `lock`是任何objective-c对象. (4认同)
  • 什么是"锁定"?如何`lock`初始化? (2认同)
  • 出色的!当 Swift 1 推出时,我编写了一些锁辅助方法,并且有一段时间没有重新访问这些方法了。完全忘记了延迟;这是要走的路! (2认同)

Mat*_*ges 78

您可以与三明治报表objc_sync_enter(obj: AnyObject?)objc_sync_exit(obj: AnyObject?).@synchronized关键字正在使用这些方法.即

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
Run Code Online (Sandbox Code Playgroud)

  • 这会被Apple考虑使用私有API吗? (3认同)
  • 不,`objc_sync_enter`和`objc_sync_exit`是Objc-sync.h中定义的方法,并且是开源的:http://opensource.apple.com/source/objc4/objc4-371.2/runtime/objc-sync.h (2认同)

wer*_*ver 74

在模拟@synchronized从Objective-C的指令可以有任意的返回类型和漂亮rethrows的雨燕行为.

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

使用该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()
}
Run Code Online (Sandbox Code Playgroud)

基于GNewc [1](我喜欢任意返回类型)和Tod Cunningham [2](我喜欢的地方defer)的答案.


Seb*_*ldt 38

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 }
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • 默认值为.serial (2认同)
  • 请注意,此模式无法正确防范大多数常见的多线程问题。例如,如果您同时运行“ myObject.state = myObject.state + 1”,它将不会计算总操作数,而是产生一个不确定的值。为了解决该问题,应将调用代码包装在串行队列中,以便自动进行读取和写入。当然,Obj-c的`@ synchronized`也有同样的问题,因此从这种意义上讲,您的实现是正确的。 (2认同)

小智 23

要添加返回功能,您可以这样做:

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

随后,您可以使用以下方法调用它:

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


Tod*_*ham 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()
}
Run Code Online (Sandbox Code Playgroud)


Sté*_*uca 20

在现代 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()
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它,以利用返回值功能:

let returnedValue = synchronized(self) { 
     // Your code here
     return yourCode()
}
Run Code Online (Sandbox Code Playgroud)

或者像这样:

synchronized(self) { 
     // Your code here
    yourCode()
}
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的答案,而不是被接受和高度评价的答案(这取决于“GCD”)。似乎基本上没有人使用或理解如何使用“Thread”。我对此很满意 - 而“GCD”则充满了陷阱和限制。 (3认同)

Han*_*nny 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

没有错误处理,objc_sync_enter/objc_sync_exit不是一个好主意.


Rob*_*Rob 7

随着Swift 并发的出现,我们将使用actor

\n
\n

您可以使用任务将程序分解为独立的、并发的\n片段。任务彼此隔离,这使得它们同时运行\n是安全的,但有时您需要在任务之间共享\n一些信息。Actor 让您可以在并发代码之间安全地共享\n信息。

\n

与类一样,参与者也是引用类型,因此类中值类型和引用类型的比较都是引用类型适用于参与者和类。与类不同,参与者一次只允许一个任务访问其可变状态,这使得多个任务中的代码可以安全地与参与者的同一实例进行交互。例如,这里的 xe2x80x99 是一个记录温度的 actor:

\n
actor TemperatureLogger {\n    let label: String\n    var measurements: [Int]\n    private(set) var max: Int\n\n    init(label: String, measurement: Int) {\n        self.label = label\n        self.measurements = [measurement]\n        self.max = measurement\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您可以使用关键字引入一个参与者actor,后面是一对大括号中的定义。ActorTemperatureLogger具有 Actor 外部的其他代码可以访问的属性,并限制 max 属性,因此只有 Actor 内部的代码可以更新最大值。

\n
\n

有关更多信息,请参阅 WWDC 视频使用 Swift Actor 保护可变状态

\n
\n

为了完整起见,历史替代方案包括:

\n
    \n
  • GCD串行队列:这是一种简单的预并发方法,可确保一次只有一个线程与共享资源交互。

    \n
  • \n
  • 具有并发 GCD 队列的读写器模式:在读写器模式中,使用并发调度队列来执行同步但并发的读取(但仅与其他读取并发,而不是写入),但使用屏障异步执行写入(强制写入)不该队列上的其他任何操作同时执行)。与简单的 GCD 串行解决方案相比,这可以提供性能改进,但在实践中,这种优势是有限的,并且是以额外的复杂性为代价的(例如,您必须小心线程爆炸场景)。恕我直言,我倾向于避免这种模式,要么坚持串行队列模式的简单性,要么当性能差异至关重要时,使用完全不同的模式。

    \n
  • \n
  • 锁:在我的 Swift 测试中,基于锁的同步往往比任何一种 GCD 方法都要快得多。锁有几种类型:

    \n
      \n
    • NSLock是一种很好的、​​相对高效的锁定机制。
    • \n
    • 在性能至关重要的情况下,我使用 \xe2\x80\x9cunfair locks\xe2\x80\x9d,但是从 Swift 使用它们时必须小心(请参阅/sf/answers/4656797001/ 1271826)。
    • \n
    • 为了完整起见,还有递归锁。NSLock恕我直言,我更喜欢简单的NSRecursiveLock。递归锁容易被滥用,并且通常会出现代码异味。
    • \n
    • 您可能会看到对 \xe2\x80\x9cspin locks\xe2\x80\x9d 的引用。许多年前,它们曾经被用在性能至关重要的地方,但现在它们已被弃用,取而代之的是不公平锁。
    • \n
    \n
  • \n
  • 从技术上讲,可以使用信号量进行同步,但它往往是所有替代方案中最慢的。

    \n
  • \n
\n

我在这里概述了一些基准测试结果

\n

简而言之,现在我将 Actor 用于当代代码库,将 GCD 串行队列用于简单场景非异步等待代码,并在那些性能至关重要的罕见情况下使用锁。

\n

而且,不用说,我们经常尝试完全减少同步次数。如果可以的话,我们经常使用值类型,其中每个线程都有自己的副本。在无法避免同步的情况下,我们会尝试尽可能减少同步的数量。

\n


DàC*_*hún 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()
Run Code Online (Sandbox Code Playgroud)

警告NSLock类使用POSIX线程来实现其锁定行为.向NSLock对象发送解锁消息时,必须确保从发送初始锁定消息的同一线程发送消息.从其他线程解锁锁定可能导致未定义的行为.


Zev*_*sVU 6

尝试:NSRecursiveLock

可以被同一个线程多次获取而不会导致死锁的锁。

let lock = NSRecursiveLock()

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

func f2() {
    lock.lock()
    defer {
        lock.unlock()
    }
    //Your Code
}
Run Code Online (Sandbox Code Playgroud)

Objective-C 同步功能支持递归和可重入代码。一个线程可以以递归方式多次使用单个信号量;其他线程被阻止使用它,直到该线程释放了所有用它获得的锁;也就是说,每个@synchronized() 块都会正常退出或通过异常退出。 来源


roc*_*ift 5

我刚刚在2018年WWDC 的“了解崩溃和崩溃日志” 会议414中找到了答案。正如conmulligan指出的,正确的方法应该是使用具有同步功能的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
          }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

与障碍同时发生,读取是异步的,写入等待先前的请求。

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

    func get(_ key: String, onResult: @escaping (UIImage?)->Void) {
        queue.async { [weak self] in
            guard let self = self else { return }
            onResult(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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


dre*_*ter 5

使用 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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

@NCCSerialized var foo: Int = 10
Run Code Online (Sandbox Code Playgroud)

或者

@NCCSerialized var myData: [SomeStruct] = []
Run Code Online (Sandbox Code Playgroud)

然后像往常一样访问变量。