Swift中的互斥锁替代方案

32

我有一个被多个线程共享的内存。我希望防止这些线程同时访问这段内存(就像生产者-消费者问题一样)。

问题

一个线程向队列中添加元素,另一个线程读取这些元素并删除它们。它们不应该同时访问队列。

解决此问题的一种方法是使用互斥锁。

据我所知,Swift 中没有互斥锁。在 Swift 中是否有任何替代方案?


1
我认为你可以查看Grand Central Dispatch,链接在这里:https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1 - 3stud1ant3
2
使用“调度队列(Dispatch Queue)”可以解决这种类型的问题。 - roy
1
@rmaddy,我编辑了我的问题。是的,我想要这样做。 - rick
2
参见 https://dev59.com/8VcO5IYBdhLWcg3wgRsG 获取一些想法。 - rmaddy
2
你可以使用调度信号量。初始化为1,在访问信号量之前/之后等待/发出信号。 - beshio
显示剩余2条评论
5个回答

22

有很多解决方案,但我在这种情况下使用串行队列:

let serialQueue = DispatchQueue(label: "queuename")
serialQueue.sync { 
    //call some code here, I pass here a closure from a method
}

编辑/更新:也适用于信号量:

let higherPriority = DispatchQueue.global(qos: .userInitiated)
let lowerPriority = DispatchQueue.global(qos: .utility)

let semaphore = DispatchSemaphore(value: 1)

func letUsPrint(queue: DispatchQueue, symbol: String) {
    queue.async {
        debugPrint("\(symbol) -- waiting")
        semaphore.wait()  // requesting the resource

        for i in 0...10 {
            print(symbol, i)
        }

        debugPrint("\(symbol) -- signal")
        semaphore.signal() // releasing the resource
    }
}

letUsPrint(queue: lowerPriority, symbol: "Low Priority Queue Work")
letUsPrint(queue: higherPriority, symbol: "High Priority Queue Work")

RunLoop.main.run()

请告诉我我的方法是否解决了您的问题,否则请提供其他方法。我也希望有一些更有效的方法来解决这个问题。 - Devanshu Saini

17

感谢beshio的评论,你可以像这样使用信号量:

let semaphore = DispatchSemaphore(value: 1)

在使用资源之前,请使用wait:

semaphore.wait()
// use the resource

并且在使用 release 之后:

semaphore.signal()

在每个线程中都执行此操作。


12

正如其他人评论的那样(包括我),有几种方法可以实现这种类型的锁。但是我认为,与其他方法相比,调度信号量更好,因为它似乎具有最小的开销。在Apple的文档中找到(Replacing Semaphore Code),除非信号量已经被锁定(=零),否则它不会进入内核空间,只有在这种情况下代码才会进入内核以切换线程。我认为大多数情况下信号量不为零(当然这取决于应用程序特定的事情)。因此,我们可以避免很多开销。

对于调度信号量的另一个评论,这是与上述情况相反的场景。如果您的线程具有不同的执行优先级,并且较高优先级的线程必须长时间锁定信号量,则调度信号量可能不是解决方案。这是因为等待线程之间没有“队列”。在这种情况下发生的情况是,较高优先级的线程大部分时间获取并锁定信号量,而较低优先级的线程只有偶尔可以锁定信号量,因此大部分时间都在等待。如果这种行为对您的应用程序不好,您必须考虑使用调度队列。


pthread互斥锁和不公平锁也支持优先级倒置的避免。 - undefined

2

您可以使用NSLock或NSRecursiveLock。如果需要从另一个锁定函数调用一个锁定函数,请使用递归版本。

class X {
  let lock = NSLock()

  func doSome() {
    lock.lock()
    defer { lock.unlock() }
    //do something here
  }

}

如果你需要从一个锁定函数中调用另一个锁定函数,我建议先停下来思考为什么你需要这样做,并可能重构你的代码,以便不需要这样做。递归互斥锁会增加开销,并且通常是创建难以解决的代码的绝佳方式。 - undefined
同意,所有类似于“mutex”的代码都可以被更好的解决方案——串行队列所取代。我认为互斥锁和类似的同步原语根本不应该暴露给Swift应用程序员。现在我们有async/await/tasks..我们只需要继续前进。 - undefined

1
在现代平台上(macOS 10.12+,iOS 10+),os_unfair_lock 是一个高效的通用互斥锁,特别适用于临界区较短的情况。它比队列要轻量得多(体积小了30倍),并且可以跟踪优先级,避免了DispatchSemaphore可能出现的倒置问题。
与大多数底层同步原语一样,它需要一个稳定的地址,所以你可以自己分配它,或者如果在较新的系统上可用,可以使用OSAllocatedUnfairLock。如果这些对你来说不是一个选择,或者你对直接使用锁不太熟悉,那么NSLock会增加一些开销,但也是一个不错的替代方案。特别是与队列或信号量相比 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接