Как справиться с состоянием гонки Читать Написать в Swift? - PullRequest
0 голосов
/ 30 августа 2018

У меня есть параллельная очередь с барьером отправки от Raywenderlich post Пример

private let concurrentPhotoQueue = DispatchQueue(label: "com.raywenderlich.GooglyPuff.photoQueue", attributes: .concurrent)

Где операции записи выполняются в

func addPhoto(_ photo: Photo) {
  concurrentPhotoQueue.async(flags: .barrier) { [weak self] in
    // 1
    guard let self = self else {
      return
    }

    // 2
    self.unsafePhotos.append(photo)

    // 3
    DispatchQueue.main.async { [weak self] in
      self?.postContentAddedNotification()
    }
  }
}

Пока операция чтения выполняется в

var photos: [Photo] {
  var photosCopy: [Photo]!

  // 1
  concurrentPhotoQueue.sync {

    // 2
    photosCopy = self.unsafePhotos
  }
  return photosCopy
}

Как это будет разрешить Состояние гонки. Вот почему только Запись выполняется с барьер и Чтение в Синхронизация . Почему чтение не сделано с барьером и запись с синхронизацией? Как и в случае Sync Write, он будет ждать, пока не будет выполнено чтение, как при блокировке, а при чтении с барьером будет выполняться только чтение.

set (10, forKey: "Number")

print (object (forKey: "Number"))

set (20, forKey: "Number")

print (object (forKey: "Number"))

public func set(_ value: Any?, forKey key: String) {
        concurrentQueue.sync {
            self.dictionary[key] = value
        }
    }

    public func object(forKey key: String) -> Any? {
        // returns after concurrentQueue is finished operation
        // beacuse concurrentQueue is run synchronously
        var result: Any?

        concurrentQueue.async(flags: .barrier) {
            result = self.dictionary[key]
        }

        return result
    }

С переворотом, я получаю ноль оба раза, с барьером на Писать, это дает 10 и 20 правильных

1 Ответ

0 голосов
/ 09 января 2019

Вы спрашиваете:

Почему чтение не выполняется с барьером ...?.

В этой схеме чтения-записи вы не используете барьер с операциями «чтения», поскольку операции чтения допускаются одновременно с другими операциями «чтения», не влияя на безопасность потоков. Таким образом, вы могли бы использовать барьер с «чтениями» (он все равно был бы потокобезопасным), но это излишне отрицательно сказывалось бы на производительности, если бы одновременно вызывалось несколько запросов «чтения». Если две операции «чтения» могут происходить одновременно по отношению друг к другу, почему бы не позволить им? Не используйте барьеры (снижающие производительность), если вам это абсолютно не нужно.

Суть в том, что только «запись» должна происходить с барьером (следя за тем, чтобы они не выполнялись одновременно в отношении «чтения» или «записи»). Но для «чтения» не требуется (или не требуется) барьер.

[Почему бы нет] ... написать с синхронизацией?

Вы могли бы «написать» с помощью sync, но, опять же, почему вы? Это только ухудшит производительность. Давайте представим, что у вас были некоторые чтения, которые еще не были сделаны, и вы отправили «запись» с барьером. Очередь отправки гарантирует для нас, что «запись», отправленная с барьером, не будет происходить одновременно с любыми другими «чтениями» или «записями», так почему код, отправивший «запись», должен сидеть и ждать «написать», чтобы закончить?

Использование sync для записи только отрицательно скажется на производительности и не дает никаких преимуществ. Вопрос не в том, «почему бы не написать с помощью sync?», А в том, «почему вы хотите, чтобы написал с помощью sync?». И ответ на этот последний вопрос: вы не хотите ждать без необходимости. Конечно, вы должны ждать «читает», а не «пишет».

Вы упомянули:

При перевороте я получаю nil ...

Да, так что давайте рассмотрим вашу гипотетическую операцию «чтения» с async:

public func object(forKey key: String) -> Any? {
    var result: Any?

    concurrentQueue.async(flags: .barrier) {
        result = self.dictionary[key]
    }

    return result
}

Этот эффект говорит: «установите переменную с именем result, отправьте задачу для ее извлечения асинхронно, , но не ждите окончания чтения, прежде чем вернуть то, что в данный момент содержится result (т.е. nil). ”

Вы можете понять, почему чтение должно происходить синхронно , потому что вы, очевидно, не можете вернуть значение до того, как обновите переменную!


Итак, переработав ваш последний пример, вы читаете синхронно без барьера, но пишете асинхронно с барьером:

public func set(_ value: Any?, forKey key: String) {
    concurrentQueue.async {
        self.dictionary[key] = value
    }
}

public func object(forKey key: String) -> Any? {
    return concurrentQueue.sync(flags: .barrier) {
        self.dictionary[key]
    }
}

Обратите внимание, поскольку метод sync в операции «чтение» будет возвращать все, что возвращает замыкание, вы можете немного упростить код, как показано выше.


Обратите внимание, если вы считаете этот шаблон чтения-записи слишком сложным, обратите внимание, что вы можете просто использовать последовательную очередь (что похоже на использование барьера как для «чтения», так и «записи»). Вы все еще, вероятно, делаете sync «читает» и async «пишет». Это тоже работает. Но в средах с высоким уровнем «чтения» это просто немного менее эффективно, чем в приведенном выше шаблоне «читатель-писатель».

...