Я искал книгу Swift, но не могу найти версию @synchronized Swift. Как сделать взаимное исключение в Swift?
removeFirst()
?
Я искал книгу Swift, но не могу найти версию @synchronized Swift. Как сделать взаимное исключение в Swift?
removeFirst()
?
Ответы:
Вы можете использовать GCD. Это немного более многословно @synchronized
, но работает как замена:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Я сам искал это и пришел к выводу, что внутри swift пока нет нативной конструкции.
Я сделал эту маленькую вспомогательную функцию на основе некоторого кода, который я видел от Мэтта Бриджеса и других.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Использование довольно просто
synced(self) {
println("This is a synchronized closure")
}
Есть одна проблема, которую я нашел с этим. Передача массива в качестве аргумента блокировки, кажется, вызывает очень тупую ошибку компилятора на этом этапе. В противном случае, хотя, кажется, работает как хотелось бы.
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
и break
заявлений больше не будет работать , чтобы выскочить из окружающей функции / петли , как было бы, если бы это было обычное утверждение.
defer
ключевого слова, чтобы гарантировать, что objc_sync_exit
вызов будет вызван, даже если closure
throws.
Мне нравятся и используются многие ответы здесь, поэтому я бы выбрал тот, который подходит вам лучше всего. Тем не менее, метод, который я предпочитаю, когда мне нужно что-то вроде target-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
это любой объект-объект.
Вы можете сэндвич заявления между 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(…)
являются открытыми заголовки , предоставляемые КСН / MacOS / и т.д.. 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()
}
Основано на ответах из GNewc [1] (где мне нравится произвольный тип возврата) и Тода Каннингема [2] (где мне нравится defer
).
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
вместо этого запускать в блоке / закрытии синхронизации, а не в самой переменной.
Используя ответ Брайана МакЛемора, я расширил его, чтобы он поддерживал объекты, которые бросают в безопасное поместье со способностью отсрочки Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
чтобы упростить использование с не-бросающими замыканиями (не нужно использовать try
), как показано в моем ответе .
Чтобы добавить возвращаемую функциональность, вы можете сделать это:
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 (). innerQueue.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 - плохая идея без обработки ошибок.
В сеансе «Понимание сбоев и журналов сбоев» 414 WWDC 2018 года они показывают следующий способ, используя DispatchQueues с синхронизацией.
В 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
}
}
}
Используйте NSLock в Swift4:
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
как чревато недостатками и ограничениями.
Попробуйте: NSRecursiveLock
Блокировка, которая может быть получена несколько раз одним потоком, не вызывая взаимоблокировку.
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, 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)")
}
}
}
С оболочками свойств 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
скрытого от пользователя. Я нашел эту ссылку, чтобы успокоиться: stackoverflow.com/a/35022486/1060314
В заключение, здесь приведем более распространенный способ, который включает возвращаемое значение или пустоту, и бросить
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
}
}