尝试使用:NSRecursiveLock
一种锁,同一线程可以多次获取而不会导致死锁。
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Swift 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并不是一个好主意。
在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并发性的出现,我们将使用actors(演员)。
You can use tasks to break up your program into isolated, concurrent pieces. Tasks are isolated from each other, which is what makes it safe for them to run at the same time, but sometimes you need to share some information between tasks. Actors let you safely share information between concurrent code.
Like classes, actors are reference types, so the comparison of value types and reference types in Classes Are Reference Types applies to actors as well as classes. Unlike classes, actors allow only one task to access their mutable state at a time, which makes it safe for code in multiple tasks to interact with the same instance of an actor. For example, here’s an actor that records temperatures:
actor TemperatureLogger { let label: String var measurements: [Int] private(set) var max: Int init(label: String, measurement: Int) { self.label = label self.measurements = [measurement] self.max = measurement } }
You introduce an actor with the
actor
keyword, followed by its definition in a pair of braces. TheTemperatureLogger
actor has properties that other code outside the actor can access, and restricts the max property so only code inside the actor can update the maximum value.
了解更多信息,请参见WWDC视频使用Swift actors保护可变状态。
为了完整起见,历史上的替代方案包括:
GCD串行队列:这是一种简单的预并发方法,确保一次只有一个线程与共享资源交互。
带有并发GCD队列的读写器模式:在读写器模式中,使用并发调度队列执行同步但并发的读取(但仅与其他读取并发),但通过障碍异步执行写入(强制写入不与该队列上的任何其他内容同时进行)。这可以比简单的GCD串行解决方案提供性能改进,但实际上,优势有限,并且需要额外的复杂性(例如,您必须小心线程爆炸场景)。在我看来,我倾向于避免使用此模式,要么坚持使用串行队列模式的简单性,要么在性能差异至关重要时使用完全不同的模式。
锁:在我的Swift测试中,基于锁的同步化往往比两种GCD方法都快得多。锁有几种类型:
NSLock
是一种不错的、相对高效的锁机制。NSLock
而不是NSRecursiveLock
。递归锁容易被滥用,通常表示代码有问题。技术上,可以使用信号量进行同步,但它往往是所有替代方案中最慢的。
我在这里概述了一些我的基准测试结果。
简而言之,现在我在当代代码库中使用actors,在简单场景非异步等待代码中使用GCD串行队列,在那些性能至关重要的罕见情况下使用锁。
不用说,我们经常尝试完全减少同步的数量。如果可以的话,我们经常使用值类型,其中每个线程都有自己的副本。在无法避免同步的情况下,我们尽可能减少这些同步的数量。
使用 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] = []
使用通常的方式访问变量。
您可以创建属性包装器 Synchronised
下面的示例使用了NSLock
。您可以使用任何您想要的同步方式,如 GCD、posix_locks 等。
@propertyWrapper public struct Synchronised<T> {
private let lock = NSLock()
private var _wrappedValue: T
public var wrappedValue: T {
get {
lock.lock()
defer {
lock.unlock()
}
return _wrappedValue
}
set {
lock.lock()
defer {
lock.unlock()
}
_wrappedValue = newValue
}
}
public init(wrappedValue: T) {
self._wrappedValue = wrappedValue
}
}
@Synchronised var example: String = "testing"
基于@drewster的回答
我会提供一个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
})
public class func synced<T>(_ lock: Any, closure: () -> T)
,它适用于void和任何其他类型。还有一些关于regrows的东西。 - hnhimport 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()
}
}
sync_enter
之后而不是之前放置 defer {sync_exit}
?在开发会议上,我听说 defer 应该放在函数内所有代码之前 :) - iTuxfinal class SpinLock {
private let lock = NSRecursiveLock()
func sync<T>(action: () -> T) -> T {
lock.lock()
defer { lock.unlock() }
return action()
}
}
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()
}
}
}
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)")
}
}
}
removeFirst()
创建包装器时,该怎么办? - ScottyBlades