Я шукав книгу Swift, але не можу знайти версію Swift @synchronized. Як зробити взаємне виключення у Swift?
removeFirst()
?
Я шукав книгу Swift, але не можу знайти версію Swift @synchronized. Як зробити взаємне виключення у Swift?
removeFirst()
?
Відповіді:
Можна використовувати GCD. Це трохи більше, ніж багатослівний @synchronized
, але працює як заміна:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Я сам це шукав і прийшов до висновку, що для цього ще немає рідної конструкції.
Я зробив цю маленьку помічну функцію на основі коду, який я бачив від Метта Бріджеса та інших.
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
кидки.
Мені подобаються і використовую тут багато відповідей, тому я б обрав те, що найкраще підходить для вас. Це означає, що метод, який я віддаю перевагу, коли мені потрібно щось на зразок aim-c, @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
lock
? Як lock
ініціалізується?
lock
є будь-який об'єкт-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 / тощо. 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
блок синхронізації / закриття замість цього, а не під самою змінною.
Щоб додати функцію повернення, ви можете зробити це:
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
}
}
Використовуючи відповідь Брайана МакЛемора, я розширив його для підтримки об’єктів, які кидають у безпечну садибу з можливістю відкладати Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
для спрощення використання із закритими замиканнями (не потрібно використовувати try
), як показано у моїй відповіді .
Швидкий 3
Цей код має можливість повторного введення і може працювати з асинхронними викликами функцій. У цьому коді після виклику someAsyncFunc () інша функція закриття послідовної черги буде оброблятися, але блокується semaphore.wait () до виклику сигналу (). 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 не є хорошою ідеєю без обробки помилок.
У сесії 414 "Розуміння збоїв та журналів збоїв" 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)
, працює для обох, недійсних та будь-якого іншого типу. Є також речі regrow.
xCode 8.3.1, швидкий 3.1
Прочитати значення запису з різних потоків (async).
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()
}
}
Навіщо ускладнювати клопоти із замками? Використовуйте диспетчерські бар'єри.
Диспетчерський бар'єр створює точку синхронізації в паралельній черзі.
Поки він працює, жоден інший блок у черзі не дозволяється запускати, навіть якщо це паралельно та інші ядра доступні.
Якщо це звучить як ексклюзивний замок (запису), це так. Безбар'єрні блоки можна розглядати як спільні (зчитувані) замки.
Поки весь доступ до ресурсу здійснюється через чергу, бар'єри забезпечують дуже дешеву синхронізацію.
На основі "Євробур" перевірити випадок підкласу
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
}
}