Вы спрашиваете:
Почему чтение не выполняется с барьером ...?.
В этой схеме чтения-записи вы не используете барьер с операциями «чтения», поскольку операции чтения допускаются одновременно с другими операциями «чтения», не влияя на безопасность потоков. Таким образом, вы могли бы использовать барьер с «чтениями» (он все равно был бы потокобезопасным), но это излишне отрицательно сказывалось бы на производительности, если бы одновременно вызывалось несколько запросов «чтения». Если две операции «чтения» могут происходить одновременно по отношению друг к другу, почему бы не позволить им? Не используйте барьеры (снижающие производительность), если вам это абсолютно не нужно.
Суть в том, что только «запись» должна происходить с барьером (следя за тем, чтобы они не выполнялись одновременно в отношении «чтения» или «записи»). Но для «чтения» не требуется (или не требуется) барьер.
[Почему бы нет] ... написать с синхронизацией?
Вы могли бы «написать» с помощью 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
«пишет». Это тоже работает. Но в средах с высоким уровнем «чтения» это просто немного менее эффективно, чем в приведенном выше шаблоне «читатель-писатель».