Что такое Swift, эквивалентный Objective-C "@synchronized"?
Я искал книгу Swift, но не могу найти версию @synchronized Swift. Как мне сделать взаимное исключение в Swift?
24 ответа
Используйте 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
использует defer
Заявление введено в Свифт 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
Вы можете сэндвич заявления между objc_sync_enter(obj: AnyObject?)
а также objc_sync_exit(obj: AnyObject?)
, Ключевое слово @synchronized использует эти методы под прикрытием. т.е.
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
Аналог @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 }
}
}
}
В современном 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 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
Чтобы добавить возвращаемую функциональность, вы можете сделать это:
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 - плохая идея без обработки ошибок.
Попробуйте: NSRecursiveLock
Блокировка, которая может быть получена несколько раз одним потоком, не вызывая взаимоблокировку.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Я только что нашел ответ в сеансе 414 "Понимание сбоев и журналов сбоев" WWDC 2018 года. Как указал conmulligan, правильный способ - использовать DispatchQueues с синхронизацией.
В Swift 4 должно быть что-то вроде следующего:
class ImageCache {
private let queue = DispatchQueue(label: "com.company.name.cache")
private var storage: [String: UIImage] = [:]
public subscript(key: String) -> UIImage? {
get {
return queue.sync {
return storage[key]
}
}
set {
queue.sync {
storage[key] = newValue
}
}
}
}
С появлением параллелизма Swift мы будем использовать акторы .
Вы можете использовать задачи, чтобы разбить вашу программу на изолированные, параллельные части. Задачи изолированы друг от друга, что делает безопасным их одновременное выполнение, но иногда вам нужно обмениваться некоторой информацией между задачами. Актеры позволяют безопасно обмениваться информацией между параллельным кодом.
Как и классы, акторы являются ссылочными типами, поэтому сравнение типов значений и ссылочных типов в разделе «Классы являются ссылочными типами» применимо как к актерам, так и к классам. В отличие от классов, акторы позволяют только одной задаче получить доступ к своему изменяемому состоянию за раз, что делает безопасным взаимодействие кода в нескольких задачах с одним и тем же экземпляром актора. Например, вот актор, который записывает температуру:
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
ключевое слово, за которым следует его определение в паре фигурных скобок.TemperatureLogger
актор имеет свойства, к которым может получить доступ другой код за пределами актора, и ограничивает свойство max, поэтому только код внутри актора может обновлять максимальное значение.
Дополнительные сведения см. в разделе Видео WWDC Защита изменяемого состояния с помощью акторов Swift .
Для полноты картины исторические альтернативы включают:
Последовательная очередь GCD: это простой предварительный параллелизмный подход, гарантирующий, что один поток за раз будет взаимодействовать с общим ресурсом.
Шаблон чтения-записи с параллельной очередью GCD: в шаблонах чтения-записи используется параллельная очередь отправки для выполнения синхронных, но параллельных операций чтения (но одновременно с другими только чтением, а не записью), но выполнение записи асинхронно с барьером (принудительная запись). не выполняться одновременно с чем-либо еще в этой очереди). Это может обеспечить повышение производительности по сравнению с простым последовательным решением GCD, но на практике преимущество скромное и достигается за счет дополнительной сложности (например, вы должны быть осторожны со сценариями взрыва потока). ИМХО, я стараюсь избегать этого паттерна, либо придерживаясь простоты паттерна последовательной очереди, либо, когда разница в производительности критична, использую совершенно другой паттерн.
Блокировки: в моих тестах Swift синхронизация на основе блокировок, как правило, значительно быстрее, чем любой из подходов GCD. Замки бывают нескольких видов:
- хороший, относительно эффективный механизм замка.
- В тех случаях, когда производительность имеет первостепенное значение, я использую «несправедливые блокировки», но вы должны быть осторожны при их использовании из Swift (см. /questions/54198727/osspinlock-ustarel-v-ios-100-vmesto-etogo-ispolzujte-osunfairlock-iz-and-ltosloc/60203152#60203152).
- Для полноты картины есть еще и рекурсивная блокировка. ИМХО, я бы предпочел простой
NSLock
надNSRecursiveLock
. Рекурсивные блокировки являются предметом злоупотреблений и часто указывают на запах кода. - Вы можете увидеть ссылки на «спин-блокировки». Много лет назад они использовались там, где производительность имела первостепенное значение, но теперь они устарели в пользу несправедливых блокировок.
Технически для синхронизации можно использовать семафоры, но это, как правило, самый медленный из всех вариантов.
Я привожу здесь несколько результатов моих тестов .
Короче говоря, в настоящее время я использую акторы для современных кодовых баз, последовательные очереди 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] = []
Затем получите доступ к переменной, как обычно.
Используйте 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 вы должны быть уверены, что сообщение отправлено из того же потока, который отправил сообщение начальной блокировки. Разблокировка блокировки из другого потока может привести к неопределенному поведению.
Вы можете создать свойствоWrapper
Synchronised
Вот пример с
NCLock
под капотом. Вы можете использовать для синхронизации все, что хотите, 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
})
В заключение, здесь приведем более распространенный способ, который включает возвращаемое значение или пустоту, и бросить
Фонд импорта
extension NSObject {
func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T
{
objc_sync_enter(lockObj)
defer {
objc_sync_exit(lockObj)
}
return try closure()
}
}
подробности
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)")
}
}
}
Что о
final class SpinLock {
private let lock = NSRecursiveLock()
func sync<T>(action: () -> T) -> T {
lock.lock()
defer { lock.unlock() }
return action()
}
}
dispatch_barrier_async - лучший способ, не блокируя текущий поток.
dispatch_barrier_async (accessQueue, {словарь [object.ID] = объект})
Зачем делать это сложно и хлопотно с замками? Используйте диспетчерские барьеры.
Барьер диспетчеризации создает точку синхронизации в параллельной очереди.
Пока он работает, ни один другой блок в очереди не может быть запущен, даже если он работает одновременно и другие ядра доступны.
Если это звучит как эксклюзивная блокировка (запись), то это так. Небарьерные блоки можно рассматривать как общие (читаемые) блокировки.
Пока весь доступ к ресурсу осуществляется через очередь, барьеры обеспечивают очень дешевую синхронизацию.
На основе "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
}
}