ios - 如何处理 Swift 中的竞争条件读/写问题?

标签 ios swift concurrency grand-central-dispatch semaphore

  1. 我从 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 操作与barrierRead 一起完成。为什么 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/

相关文章:

ios - 在 Collection View 上显示复选标记

ios - 创建新问题 GitHub iOS

iOS应用程序更新方法

swift - 如何从 Swift 中的段落开始到 UITextView

java - 哲学家就餐 - 最后一个线程未正确终止

iphone - 使用 GCD 填充 TableView 单元格

ios - 单击切换按钮展开/折叠 UITableViewCell 高度

java - Web 服务器关闭时关闭 Web 应用程序中的 TaskExecutor

Java - 保存单实例集合的有效方法

ios - iOS 的 Firebase 云消息传递统计信息