我从 Raywenderlich post Example
得到了一个带有调度屏障的并发队列private let concurrentPhotoQueue = DispatchQueue(标签:“com.raywenderlich.GooglyPuff.photoQueue”,属性:.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 操作与barrier 和Read 一起完成。为什么 Read 没有完成 barrier 和 write with sync ?与Sync Write一样,它会像锁一样等待读取,而barrier Read只会进行读取操作。
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
}
通过翻转行为,我两次都得到 nil,Write 上有障碍,它给出了 10 和 20 正确
最佳答案
你问:
Why is Read not done with barrier ... ?.
在这种读写器模式中,您不对“读取”操作使用屏障,因为读取允许与其他“读取”并发发生,而不会影响线程安全。允许并发读取是读写器模式背后的全部激励思想。
因此,您可以对“读取”使用屏障(它仍然是线程安全的),但如果恰好同时调用多个“读取”请求,它会对性能产生不必要的负面影响时间。如果两个“读”操作可以同时发生,为什么不让它们发生呢?除非绝对需要,否则不要使用障碍(降低性能)。
最重要的是,只有“写入”需要与屏障一起发生(确保它们不会与任何“读取”或“写入”同时完成)。但是“读取”不需要(或不需要)障碍。
[Why not] ... write with sync?
您可以使用sync
“写入”,但是,再说一次,您为什么要这么做?它只会降低性能。假设您有一些尚未完成的读取,并且您发送了一个带有屏障的“写入”。调度队列将为我们确保使用屏障调度的“写入”不会与任何其他“读取”或“写入”同时发生,那么为什么调度“写入”的代码应该坐在那里等待“写”完了?
使用sync
进行写入只会对性能产生负面影响,并没有任何好处。问题不是“为什么不用 sync
来写?”而是“为什么你想要用sync
来写?”后一个问题的答案是,您不想不必要地等待。当然,您必须等待“读取”,而不是“写入”。
你提到:
With the flip behavior, I am getting
nil
...
是的,让我们考虑一下您假设的使用 async
的“读取”操作:
public func object(forKey key: String) -> Any? {
var result: Any?
concurrentQueue.async {
result = self.dictionary[key]
}
return result
}
这实际上是说“设置一个名为 result
的变量,分派(dispatch)任务以异步方式检索它,但不要等待读取完成才返回任何 result
当前包含(即 nil
)。”
您可以明白为什么读取必须同步发生,因为您显然不能在更新变量之前返回值!
因此,修改后一个示例,您可以无障碍地同步读取,但有障碍地异步写入:
public func object(forKey key: String) -> Any? {
return concurrentQueue.sync {
self.dictionary[key]
}
}
public func set(_ value: Any?, forKey key: String) {
concurrentQueue.async(flags: .barrier) {
self.dictionary[key] = value
}
}
请注意,因为“读取”操作中的 sync
方法将返回闭包返回的任何内容,所以您可以大大简化代码,如上所示。
或者,就我个人而言,与其使用 object(forKey:)
和 set(_:forKey:)
,不如我自己写 subscript operator :
public subscript(key: String) -> Any? {
get {
concurrentQueue.sync {
dictionary[key]
}
}
set {
concurrentQueue.async(flags: .barrier) {
self.dictionary[key] = newValue
}
}
}
然后你可以这样做:
store["Number"] = 10
print(store["Number"])
store["Number"] = 20
print(store["Number"])
请注意,如果您觉得这种读写器模式太复杂,请注意您可以只使用串行队列(这就像对“读”和“写”都使用屏障一样)。您仍然可能会执行 sync
“读取”和 async
“写入”。那也行。但在“读取”竞争激烈的环境中,它的效率略低于上述读写器模式。
关于ios - 如何处理 Swift 中的竞争条件读/写问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52097573/