如何处理 Swift 中的 Race Condition Read/Write 问题?
How to handle Race Condition Read/Write Problem in Swift?
我从 Raywenderlich post Example[=16 得到了一个带有调度屏障的并发队列=]
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
}
因为这将解决 竞争条件。这里为什么只有 Write 操作在 barrier 和 Read 中完成 Sync。为什么 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 ... ?.
在这个 reader-writer 模式中,您不对“读取”操作使用屏障,因为读取 允许与其他“读取”同时发生”,而不影响线程安全。这是 reader-writer 模式背后的全部激励思想,允许并发读取。
因此,您可以对“读取”使用屏障(它仍然是线程安全的),但如果多个“读取”请求同时发生,它会对性能产生不必要的负面影响同时被调用。如果两个“读”操作可以同时发生,为什么不让它们发生呢?除非绝对需要,否则不要使用障碍(降低性能)。
最重要的是,只有“写入”需要与屏障一起发生(确保它们不会与任何“读取”或“写入”同时完成)。但是“读取”不需要(或不需要)障碍。
[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
的变量,分派任务以异步方式 检索它, 但不要在 [= 之前等待读取完成80=]ing 当前包含的任何 result
(即 nil
)。”
你可以明白为什么读取必须同步发生,因为你显然不能在更新变量之前return一个值!
因此,修改后一个示例,您可以无障碍同步读取,但有障碍异步写入:
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
方法将return无论闭包returns,你可以大大简化代码,如上所示。
或者,就个人而言,而不是 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"])
注意,如果你觉得这个 reader-writer 模式太复杂,请注意你可以只使用串行队列(这就像为“读”和“写”使用屏障)。您仍然可能会 sync
“读取”和 async
“写入”。那也行。但在“读取”竞争激烈的环境中,它的效率仅比上述 reader-writer 模式低一点。
我从 Raywenderlich post Example[=16 得到了一个带有调度屏障的并发队列=]
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
}
因为这将解决 竞争条件。这里为什么只有 Write 操作在 barrier 和 Read 中完成 Sync。为什么 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 ... ?.
在这个 reader-writer 模式中,您不对“读取”操作使用屏障,因为读取 允许与其他“读取”同时发生”,而不影响线程安全。这是 reader-writer 模式背后的全部激励思想,允许并发读取。
因此,您可以对“读取”使用屏障(它仍然是线程安全的),但如果多个“读取”请求同时发生,它会对性能产生不必要的负面影响同时被调用。如果两个“读”操作可以同时发生,为什么不让它们发生呢?除非绝对需要,否则不要使用障碍(降低性能)。
最重要的是,只有“写入”需要与屏障一起发生(确保它们不会与任何“读取”或“写入”同时完成)。但是“读取”不需要(或不需要)障碍。
[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
的变量,分派任务以异步方式 检索它, 但不要在 [= 之前等待读取完成80=]ing 当前包含的任何 result
(即 nil
)。”
你可以明白为什么读取必须同步发生,因为你显然不能在更新变量之前return一个值!
因此,修改后一个示例,您可以无障碍同步读取,但有障碍异步写入:
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
方法将return无论闭包returns,你可以大大简化代码,如上所示。
或者,就个人而言,而不是 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"])
注意,如果你觉得这个 reader-writer 模式太复杂,请注意你可以只使用串行队列(这就像为“读”和“写”使用屏障)。您仍然可能会 sync
“读取”和 async
“写入”。那也行。但在“读取”竞争激烈的环境中,它的效率仅比上述 reader-writer 模式低一点。