什么是Swift相当于Objective-C's &;@synchronized"?

我已经搜索了Swift的书籍,但找不到@synchronized的Swift版本。我如何做互斥在Swift?

129650 次浏览

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

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

你可以使用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")
}

我发现了一个问题。在这一点上,传入一个数组作为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!

要添加返回函数,你可以这样做:

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

另一种方法是创建一个超类,然后继承它。这样你可以更直接地使用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
}
}

为什么要让锁变得困难和麻烦?

调度屏障在并发队列中创建一个同步点。

当它正在运行时,队列上的其他块都不允许运行,即使它是并发的并且其他内核可用。

如果这听起来像一个排他(写)锁,它是。 无障碍块可以被认为是共享(读)锁。

只要所有对资源的访问都是通过队列执行的,barrier就提供了非常便宜的同步

我喜欢并使用了这里的许多答案,所以我会选择最适合你的。也就是说,当我需要objective-c的@synchronized之类的东西时,我更喜欢使用swift 2中引入的defer语句。

{
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

使用Bryan McLemore的答案,我扩展了它,以支持使用Swift 2.0延迟能力扔进安全庄园的对象。

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}


try block()
}

在Swift中,类似于Objective-C中的@synchronized指令可以有任意的返回类型和良好的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()
}

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

基于ɲ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

Dispatch_barrier_async是更好的方法,同时不会阻塞当前线程。

< p > dispatch_barrier_async (accessQueue, { 字典对象。ID = object }) < / p >

总之,这里给出了更常见的方法,包括返回值或void和throw

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




}

斯威夫特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 }
}
}
}

斯威夫特3

此代码具有重入能力,可以与异步函数调用一起工作。在这段代码中,someAsyncFunc()被调用之后,串行队列上的另一个函数闭包将被处理,但会被semapore .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不是一个好主意。

细节

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

在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()
< p >警告 NSLock类使用POSIX线程来实现它的锁定行为。当向NSLock对象发送解锁消息时,必须确保该消息是从发送初始锁定消息的同一个线程发送的。从不同线程解锁锁会导致未定义的行为

在2018年WWDC的“理解崩溃和崩溃日志”414年会议中,他们展示了以下使用DispatchQueues与sync的方法。

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

图我将发布我的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
})

试题:NSRecursiveLock

同一个线程可以多次获得的锁 导致死锁

let lock = NSRecursiveLock()


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


func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Objective-C同步特性支持递归和 可重入代码。线程可以多次使用同一个信号量 递归的方式;其他线程被阻止使用它,直到 线程释放使用它获得的所有锁;也就是说,每一个 @synchronized()块正常退出或通过异常退出。 < / p >

在现代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()
}

使用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] = []

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

是什么

final class SpinLock {
private let lock = NSRecursiveLock()


func sync<T>(action: () -> T) -> T {
lock.lock()
defer { lock.unlock() }
return action()
}
}

你可以创建propertyWrapper 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的答案

随着斯威夫特并发的出现,我们将使用演员

你可以使用任务把你的程序分解成独立的、并发的 碎片。任务是相互隔离的,这就是为什么 它们同时运行是安全的,但有时你需要共享 任务之间的一些信息。演员让你安全地分享

和类一样,actor也是引用类型,所以比较值 类是引用类型中的类型和引用类型适用于 演员和类。与类不同,角色只允许一个任务 一次访问它们的可变状态,这对代码来说是安全的 在多个任务中与参与者的同一个实例进行交互。为 例如,这是一个记录温度的actor:

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

你用actor关键字引入一个actor,后面跟着它的定义,用一对大括号表示。TemperatureLogger参与者具有参与者外部的其他代码可以访问的属性,并限制了max属性,因此只有参与者内部的代码才能更新最大值。

更多信息,请参见WWDC视频使用Swift角色保护可变状态


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

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

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

  • 锁:在我的Swift测试中,基于锁的同步往往比任何一种GCD方法都快。锁有几种口味:

    • NSLock是一个不错的,相对有效的锁定机制。
    • 在性能是最重要的情况下,我使用“不公平锁”,但从Swift使用它们时必须小心(参见https://stackoverflow.com/a/66525671/1271826)。
    • 为了完整起见,还有递归锁。恕我直言,我更喜欢简单的NSLock而不是NSRecursiveLock。递归锁容易被滥用,并且经常显示出代码气味。
    • 你可能会看到“旋转锁”的引用。许多年前,它们常常被用于性能最重要的地方,但现在不赞成使用不公平锁。
  • 从技术上讲,可以使用信号量进行同步,但它往往是所有替代方法中最慢的。

我概述了一些我的基准测试结果

简而言之,现在我在当代代码库中使用actor,在简单的非异步等待代码中使用GCD串行队列,在性能至关重要的极少数情况下使用锁。

不用说,我们经常尝试减少同步的数量。如果可以,我们通常使用值类型,其中每个线程获得自己的副本。在无法避免同步的情况下,我们会尽量减少同步的数量。