我已经搜索过Swift书,但是找不到@synchronized的Swift版本。如何在Swift中进行互斥?
removeFirst()
怎么办?
我已经搜索过Swift书,但是找不到@synchronized的Swift版本。如何在Swift中进行互斥?
removeFirst()
怎么办?
Answers:
您可以使用GCD。它比更加冗长@synchronized
,但可以代替:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
我一直在寻找自己,得出的结论是,在此之前,还没有原生的构造。
我确实根据我从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!
@synchronized
很好地保留了该块的语法,但是请注意,它与诸如@synchronized
Objective-C中的块之类的真实内置块语句并不相同,因为return
and break
语句不再能像这样跳出周围的函数/循环如果这是一个普通的声明,那将是。
defer
关键字以确保objc_sync_exit
即使closure
引发也被调用的好地方。
我喜欢并在这里使用许多答案,因此,我将选择最适合您的一个。就是说,当我需要诸如Objective-C之类的东西时,我偏爱的方法@synchronized
使用了defer
Swift 2中引入的语句。
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
这个方法的好处是,你的关键部分可以退出所希望的任何方式包含块(例如return
,break
,continue
,throw
),和“defer语句中的语句执行不管程序控制的传输方式。” 1个
lock
啊 如何lock
初始化?
lock
是任何Objective-C对象。
您可以在objc_sync_enter(obj: AnyObject?)
和之间夹入语句objc_sync_exit(obj: AnyObject?)
。@synchronized关键字在幕后使用了这些方法。即
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
并且objc_sync_exit
在Objc-sync.h定义的方法,并开放源码:opensource.apple.com/source/objc4/objc4-371.2/runtime/...
objc_sync_enter(…)
&objc_sync_exit(…)
是iOS / macOS / etc提供的公共标头。API (看起来好像它们….sdk
位于路径中usr/include/objc/objc-sync.h
)。找出某个东西是否是公共API的最简单方法是(在Xcode中)键入函数名称(例如objc_sync_enter()
,无需为C函数指定参数),然后尝试用命令单击它。如果它向您显示该API的头文件,那么您就很好了(因为如果它不是公共头,将无法看到该头)。
@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()
}
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 }
}
}
}
.serial
似乎不可用。但是.concurrent
可用。:/
myObject.state = myObject.state + 1
同时运行,它将不会计算总操作数,而是会产生不确定的值。为了解决该问题,应将调用代码包装在串行队列中,以使读取和写入都自动进行。当然,Obj-c @synchronised
存在相同的问题,因此在这种意义上,您的实现是正确的。
myObject.state += 1
是读操作然后写操作的组合。其他一些线程仍然可以介于两者之间来设置/写入值。根据objc.io/blog/2018/12/18/atomic-variables,set
在同步块/闭包中而不是在变量本身下运行会更容易。
要添加返回功能,可以执行以下操作:
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
}
}
迅捷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并不是一个好主意。
在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
}
}
}
在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 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()
}
GCD
)。似乎基本上没有人使用或不知道如何使用Thread
。我对它感到满意-但GCD
充满了陷阱和局限性。
一个锁可以由同一线程多次获取而不会导致死锁。
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
图我将发布基于先前答案构建的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也适用于其他任何类型。也有再生的东西。
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)")
}
}
}
使用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] = []
然后像往常一样访问变量。
DispatchQueue
对用户隐藏的副作用。我发现此SO参考使我放心:stackoverflow.com/a/35022486/1060314
总之,这里给出了更常见的方法,包括返回值或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()
}
}
基于“ 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
另一种方法是创建一个超类,然后继承它。这样您可以更直接地使用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
}
}