Что такое Swift, эквивалентный «@synchronized» Objective-C?

181

Я искал книгу Swift, но не могу найти Swift-версию @synchronized. Как сделать взаимное исключение в Swift?

  • 1
    Я бы использовал диспетчерский барьер. Барьеры обеспечивают очень дешевую синхронизацию. dispatch_barrier_async (). и т.п.
  • 0
    @ FrederickC.Lee, что если вам нужно синхронизировать запись , например, при создании оболочки для removeFirst() ?
Теги:
concurrency
mutex

16 ответов

161
Лучший ответ

Я использую GCD. Это немного более подробно, чем @synchronized, но отлично работает как замена:

let lockQueue = dispatch_queue_create("com.test.LockQueue", nil)
dispatch_sync(lockQueue) {
    // code
}
  • 8
    Это замечательно, но не хватает возможности повторного входа, которую вы имеете с @synchronized.
  • 0
    Я попробовал этот метод. Но он не заблокирован должным образом. Я не знаю почему. objc_sync_enter / objc_sync_exit, кажется, работает правильно.
Показать ещё 11 комментариев
152

Я искал это сам и пришел к выводу, что внутри этого не существует встроенной встроенной конструкции.

Я сделал эту небольшую вспомогательную функцию, основанную на некотором коде, который я видел у Matt Bridges и других.

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!
  • 0
    Ницца! Пожалуйста, напишите ошибку для этого, если это все еще проблема в 1.0
  • 12
    Это довольно полезно и хорошо сохраняет синтаксис блока @synchronized , но обратите внимание, что он не идентичен реальному встроенному выражению блока, @synchronized блок @synchronized в Objective-C, потому что операторы return и break больше не работают, чтобы выпрыгнуть из окружающая функция / цикл, как если бы это было обычное утверждение.
Показать ещё 9 комментариев
119

Мне нравятся и используют многие ответы здесь, поэтому я бы выбрал то, что лучше всего подходит для вас. Тем не менее, метод, который я предпочитаю, когда мне нужно что-то вроде 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 statement выполняются независимо от того, как передается программный контроль". 1

  • 0
    Я думаю, что это, вероятно, самое элегантное решение, представленное здесь. Спасибо за ваш отзыв.
  • 2
    Что такое lock ? Как инициализируется lock ?
Показать ещё 3 комментария
67

Вы можете сэндвич-выражениями между objc_sync_enter(obj: AnyObject?) и objc_sync_exit(obj: AnyObject?). Ключевое слово @synchronized использует эти методы под обложками. то есть.

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
  • 3
    Будет ли это рассматриваться как использование частного API Apple?
  • 1
    @ Друкс продолжит?
Показать ещё 4 комментария
62

Аналог директивы @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] (где мне нравится произвольный тип возврата) и Tod Cunningham [2] (где мне нравится defer).

  • 0
    Xcode сообщает мне, что @noescape теперь используется по умолчанию и устарел в Swift 3.
  • 0
    Правильно, код в этом ответе предназначен для Swift 2 и требует некоторой адаптации для Swift 3. Я обновлю его, когда у меня будет время.
Показать ещё 3 комментария
23

Чтобы добавить функцию возврата, вы можете сделать это:

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

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 }
        }
    }
} 
  • 0
    Это не похоже на работу с XCode8.1. .serial кажется, недоступен. Но .concurrent доступен. : /
  • 2
    по умолчанию .serial
Показать ещё 1 комментарий
20

Используя ответ Брайана МакЛемора, я продлил его, чтобы поддерживать объекты, которые бросают надежную усадьбу с возможностью замедления Swift 2.0.

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

    try block()
}
  • 0
    Как было показано в моем ответе, было бы лучше использовать rethrows чтобы упростить использование с замыканиями без бросков (не нужно использовать try ).
  • 0
    Обновлено до отбрасывает .... спасибо werediver
8

Swift 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 не является хорошей идеей без обработки ошибок.

  • 0
    Какая обработка ошибок? Компилятор не допустит ничего, что выбрасывает. С другой стороны, не используя objc_sync_enter / exit, вы отказываетесь от существенного прироста производительности.
3

Используйте 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 вы должны быть уверены, что сообщение отправлено из того же потока, который отправил исходное сообщение блокировки. Разблокировка блокировки из другого потока может привести к поведению undefined.

0

Подробнее

xКод 8.3.1, быстрый 3.1

Task

Чтение значения записи из разных потоков (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
}

Полный образец

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

В заключение здесь приведены более распространенные способы, которые включают возвращаемое значение или 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()
    }


}
0

dispatch_barrier_async - лучший способ, не блокируя текущий поток.

dispatch_barrier_async (accessQueue, {                       словарь [object.ID] = объект               })

0

Основываясь на ɲ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
0

Почему это затрудняет и затрудняет блокировки? Используйте Dispatch Barriers.

С помощью диспетчерского барьера создается точка синхронизации в параллельной очереди.

При его запуске ни один другой блок в очереди не может работать, даже если его параллельные и другие ядра доступны.

Если это звучит как блокировка (запись), это так. Небарьерные блоки можно рассматривать как общие (прочитанные) блокировки.

Пока весь доступ к ресурсу выполняется через очередь, барьеры обеспечивают очень дешевую синхронизацию.

  • 1
    Я имею в виду, что вы предполагаете использовать очередь GCD для синхронизации доступа, но это не упоминалось в первоначальном вопросе. А барьер необходим только для параллельной очереди - вы можете просто использовать последовательную очередь, чтобы поставить в очередь взаимно исключенные блоки для эмуляции блокировки.
  • 0
    Мой вопрос, зачем эмулировать блокировку? Из того, что я прочитал, блокировки не приветствуются из-за накладных расходов по сравнению с барьером в очереди.
-3

Другой метод - создать суперкласс и затем наследовать его. Таким образом, вы можете напрямую использовать 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
        }
    }
  • 8
    -1 Наследование дает вам полиморфизм подтипа в обмен на увеличение связи. Избегайте позже, если вам не нужно первое. Не ленись. Предпочитаю композицию для повторного использования кода.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню