Подтвердить что ты не робот

Objc_sync_enter/objc_sync_exit не работает с DISPATCH_QUEUE_PRIORITY_LOW

Мне нужно чтение\запись для моего приложения. Я прочитал https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock

и написал мой собственный класс, потому что в Swift нет блокировки чтения/записи

class ReadWriteLock {

    var logging = true
    var b = 0
    let r = "vdsbsdbs" // string1 for locking
    let g = "VSDBVSDBSDBNSDN" // string2 for locking

    func waitAndStartWriting() {
        log("wait Writing")
        objc_sync_enter(g)
        log("enter writing")
    }


    func finishWriting() {
        objc_sync_exit(g)
        log("exit writing")
    }

    // ждет пока все чтение завершится чтобы начать чтение
    // и захватить мютекс
    func waitAndStartReading() {

        log("wait reading")
        objc_sync_enter(r)
        log("enter reading")
        b++
        if b == 1 {
            objc_sync_enter(g)
            log("read lock writing")
        }

        print("b = \(b)")
        objc_sync_exit(r)
    }


    func finishReading() {

        objc_sync_enter(r)

        b--

        if b == 0 {
            objc_sync_exit(g)
            log("read unlock writing")
        }

        print("b = \(b)")
        objc_sync_exit(r)
    }

    private func log(s: String) {
        if logging {
            print(s)
        }
    }
}

Он работает хорошо, пока я не попытаюсь использовать его из потоков GCD.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

Когда я пытаюсь использовать этот класс из разных асинхронных блоков, в какой-то момент он позволяет писать, когда запись заблокирована

Вот пример журнала:

wait reading
enter reading
read lock writing
b = 1
wait reading
enter reading
b = 2
wait reading
enter reading
b = 3
wait reading
enter reading
b = 4
wait reading
enter reading
b = 5
wait reading
enter reading
b = 6
wait reading
enter reading
b = 7
wait reading
enter reading
b = 8
wait reading
enter reading
b = 9
b = 8
b = 7
b = 6
b = 5
wait Writing
enter writing
exit writing
wait Writing
enter writing 

Итак, как вы видите, g заблокирован, но objc_sync_enter (g) позволяет продолжить. Почему это может случиться?

Кстати, я проверил, сколько раз построил ReadWriteLock, и он 1.

Почему objc_sync_exit не работает и разрешает objc_sync_enter (g), когда он не освобожден?

PS Readwirtelock определяется как

class UserData {

    static let lock = ReadWriteLock()

Спасибо.

4b9b3361

Ответ 1

objc_sync_enter является примитивом крайне низкого уровня и не предназначен для непосредственного использования. Это деталь реализации старой системы @synchronized в ObjC. Даже это крайне устарело и, как правило, следует избегать.

Синхронизированный доступ в Cocoa лучше всего достигается с помощью очередей GCD. Например, это общий подход, который обеспечивает блокировку чтения/записи (одновременное чтение, эксклюзивное письмо).

public class UserData {
    private let myPropertyQueue = dispatch_queue_create("com.example.mygreatapp.property", DISPATCH_QUEUE_CONCURRENT)

    private var _myProperty = "" // Backing storage
    public var myProperty: String {
        get {
            var result = ""
            dispatch_sync(myPropertyQueue) {
                result = self._myProperty
            }
            return result
        }

        set {
            dispatch_barrier_async(myPropertyQueue) {
                self._myProperty = newValue
            }
        }
    }
}

Все ваши одновременные свойства могут совместно использовать одну очередь, или вы можете предоставить каждому свойству свою очередь. Это зависит от того, сколько вы ожидаете (писатель заблокирует всю очередь).

"Барьер" в "dispatch_barrier_async" означает, что это единственное, что разрешено запускать в очереди в то время, поэтому все предыдущие чтения будут завершены, и все будущие чтения будут предотвращены до тех пор, пока они не будут завершены. Эта схема означает, что у вас может быть столько одновременных читателей, сколько вы хотите, без голодающих писателей (поскольку писатели всегда будут обслуживаться), а записи никогда не блокируются. При чтении блокируется, и только если есть фактическое утверждение. В обычном, неоспоримом случае это очень быстро.

Ответ 2

Вы уверены, что ваши блоки фактически выполняются на разных потоках?

objc_sync_enter()/objc_sync_exit() защищают вас только от доступа к объекту из разных потоков. Они используют рекурсивный мьютекс под капотом, поэтому они не будут либо заторможены, либо не позволят вам повторно обращаться к объекту из того же потока.

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

Ответ 3

Это один из тех тончайших нюансов, который легко пропустить.

Замки в Свифте

Вы должны быть очень осторожны, используя замок. В Swift String - это структура, то есть передача по значению.

Всякий раз, когда вы вызываете objc_sync_enter(g), вы даете не g, а копию g. Таким образом, каждый поток по сути создает свою собственную блокировку, которая, по сути, похожа на отсутствие блокировки вообще.

Использовать NSObject

Вместо использования String или Int, используйте простой NSObject.

let lock = NSObject()

func waitAndStartWriting() {
    log("wait Writing")
    objc_sync_enter(lock)
    log("enter writing")
}


func finishWriting() {
    objc_sync_exit(lock)
    log("exit writing")
}

Это должно заботиться об этом!

Ответ 4

У меня была такая же проблема с использованием очередей в фоновом режиме. Синхронизация не работает все время в очередях с "фоновым" (низким) приоритетом.

Одно исправление, которое я нашел, состояло в том, чтобы использовать семафоры вместо "obj_sync":

static private var syncSemaphores: [String: DispatchSemaphore] = [:]

    static func synced(_ lock: String, closure: () -> ()) {

        //get the semaphore or create it
        var semaphore = syncSemaphores[lock]
        if semaphore == nil {
            semaphore = DispatchSemaphore(value: 1)
            syncSemaphores[lock] = semaphore
        }

        //lock semaphore
        semaphore!.wait()

        //execute closure
        closure()

        //unlock semaphore
        semaphore!.signal()
    }

Идея функции исходит из того, что Swift эквивалентно "@synchronized" Objective-C? , ответ @bryan-mclemore.

Ответ 5

В дополнение к решению @rob-napier. Я обновил его до Swift 5.1, добавил универсальную типизацию и несколько удобных методов добавления. Обратите внимание, что только методы, которые обращаются к resultArray через get/set или append, являются потокобезопасными, поэтому я добавил параллельное добавление также для моего случая практического использования, когда данные результатов обновляются во многих вызовах результатов из экземпляров Operation.

public class ConcurrentResultData<E> {

    private let resultPropertyQueue = dispatch_queue_concurrent_t.init(label: UUID().uuidString)
    private var _resultArray = [E]() // Backing storage

    public var resultArray:  [E] {
        get {
            var result = [E]()
            resultPropertyQueue.sync {
                result = self._resultArray
            }
            return result
        }
        set {
            resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
                self._resultArray = newValue
            }
        }
    }

    public func append(element : E) {
        resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
            self._resultArray.append(element)
        }
    }

    public func appendAll(array : [E]) {
        resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
            self._resultArray.append(contentsOf: array)
        }
    }

}

Для примера бега на детской площадке добавьте

//MARK:- helpers
var count:Int = 0
let numberOfOperations = 50

func operationCompleted(d:ConcurrentResultData<Dictionary<AnyHashable, AnyObject>>) {
    if count + 1 < numberOfOperations {
        count += 1
    }
    else {
        print("All operations complete \(d.resultArray.count)")
        print(d.resultArray)
    }
}

func runOperationAndAddResult(queue:OperationQueue, result:ConcurrentResultData<Dictionary<AnyHashable, AnyObject>> ) {
    queue.addOperation {
        let id = UUID().uuidString
        print("\(id) running")
        let delay:Int = Int(arc4random_uniform(2) + 1)
        for _ in 0..<delay {
            sleep(1)
        }
        let dict:[Dictionary<AnyHashable, AnyObject>] = [[ "uuid" : NSString(string: id), "delay" : NSString(string:"\(delay)") ]]
        result.appendAll(array:dict)
        DispatchQueue.main.async {
            print("\(id) complete")
            operationCompleted(d:result)
        }
    }
}

let q = OperationQueue()
let d = ConcurrentResultData<Dictionary<AnyHashable, AnyObject>>()
for _ in 0..<10 {
    runOperationAndAddResult(queue: q, result: d)
}