如何在Swift中处理种族条件读写 [英] How to handle Race Condition Read Write in Swift?

查看:67
本文介绍了如何在Swift中处理种族条件读写的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Raywenderlich 中发布了一个带有调度障碍的并发队列。示例

I have got a concurrent queue with dispatch barrier from Raywenderlich post Example

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 中的 barrier Read 执行写入操作。为什么不使用屏障完成读取并使用同步完成写入?与Sync Write一样,它会一直等到读起来像是一个锁,而屏障读取则只会进行读操作。

As this will resolve Race Condition. Here why only Write operation is done with barrier and Read in Sync. Why is Read not done with barrier and write with sync ?. As with Sync Write, it will wait till it reads like a lock and while barrier Read it will only be read operation.


set(10,forKey:" ; Number'')

set(10, forKey: "Number")

print(object(forKey:" Number"))

print(object(forKey: "Number"))

set(20,forKey:" Number")

set(20, forKey: "Number")

print(object(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
    }

通过翻转行为,我两次都得到零,并且在Write上设置了10& 20个正确的

With the flip behavior, I am getting nil both times, with barrier on Write it is giving 10 & 20 correct

推荐答案

您问:


为什么不使用barrier来读取... ....

Why is Read not done with barrier ... ?.

在这种读写模式中,您不使用barrier使用读取操作,因为允许读取 与其他读取同时发生,而不影响线程安全性。因此,您可以可以将屏障与读取一起使用(它仍然是线程安全的),但是如果碰巧同时调用多个读取请求,则会不必要地对性能产生负面影响。如果两个读取操作可以彼此同时发生,为什么不让它们呢?除非绝对必要,否则请不要使用障碍物(降低性能)。

In this reader-writer pattern, you don’t use barrier with "read" operations because reads are allowed to happen concurrently with respect to other "reads", without impacting thread-safety. So, you could use barrier with "reads" (it would still be thread-safe), but it would unnecessarily negatively impact performance if multiple "read" requests happened to be called at the same time. If two "read" operations can happen concurrently with respect to each other, why not let them? Don’t use barriers (reducing performance) unless you absolutely need to.

最重要的是,只有写入才需要发生障碍(确保它们不会在读取或写入的同时完成)。

Bottom line, only "writes" need to happen with barrier (ensuring that they’re not done concurrently with respect to any "reads" or "writes"). But no barrier is needed (or desired) for "reads".


[为什么不......同步写入?

[Why not] ... write with sync?

可以通过 sync 写,但是,再一次,你为什么呢?这只会降低性能。假设您有一些尚未完成的读取,并且调度了带有屏障的写入。派遣队列将确保我们不会与其他读或写同时发生带有屏障的写,所以为什么派遣该写的代码应该坐在那里等待

You could "write" with sync, but, again, why would you? It would only degrade performance. Let’s imagine that you had some reads that were not yet done and you dispatched a "write" with a barrier. The dispatch queue will ensure for us that a "write" dispatched with a barrier won’t happen concurrently with respect to any other "reads" or "writes", so why should the code that dispatched that "write" sit there and wait for the "write" to finish?

使用 sync 进行写操作只会对性能产生负面影响,并且没有任何好处。问题不是为什么不使用 sync 编写?而是为什么您要想要 sync 来写作?对后一个问题的答案是,您不想等待不必要的等待。当然,您必须等待读取,而不是写入。

Using sync for writes would only negatively impact performance, and offers no benefit. The question is not "why not write with sync?" but rather "why would you want to write with sync?" And the answer to that latter question is, you don’t want to wait unnecessarily. Sure, you have to wait for "reads", but not "writes".

您提到:


通过翻转行为,我得到 nil ...

是的,因此,请考虑使用 async 进行假设的读取操作:

Yep, so lets consider your hypothetical "read" operation with async:

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

    concurrentQueue.async {
        result = self.dictionary[key]
    }

    return result
}

此有效表示设置一个名为 result 的变量,将任务分派给异步检索,但是在返回当前包含的任何结果之前,不要等待读取完成(即, nil )。

This effective says "set up a variable called result, dispatch task to retrieve it asynchronously, but don’t wait for the read to finish before returning whatever result currently contained (i.e., nil)."

您可以了解为什么必须同步进行读取,因为在更新变量之前,您显然无法返回值!

You can see why reads must happen synchronously, because you obviously can’t return a value before you update the variable!

因此,重做您的后一个示例,您可以同步阅读而没有障碍,但是可以异步编写障碍。

So, reworking your latter example, you read synchronously without barrier, but write asynchronously with barrier:

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 方法将返回闭包返回的内容,您可以大大简化代码,如上所示。

Note, because sync method in the "read" operation will return whatever the closure returns, you can simplify the code quite a bit, as shown above.

或者个人而言,而不是 object(forKey:) set(_:forKey:),我只写自己的下标运算符

Or, personally, rather than object(forKey:) and set(_:forKey:), I’d just write my own subscript operator:

public subscript(key: String) -> Any? {
    get {
        concurrentQueue.sync { 
            dictionary[key] 
        } 
    }

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

然后您可以执行以下操作:

Then you can do things like:

store["Number"] = 10
print(store["Number"])
store["Number"] = 20
print(store["Number"])






请注意,如果您发现此读写模式过于复杂,请注意,您可以使用串行队列(就像对读取和写入使用屏障一样)。您仍然可能会执行 sync 读取和 async 写入。那也行。但是在争用高的读环境中,它的效率只比上面的读写器模式低一点。


Note, if you find this reader-writer pattern too complicated, note that you could just use a serial queue (which is like using a barrier for both "reads" and "writes"). You’d still probably do sync "reads" and async "writes". That works, too. But in environments with high contention "reads", it’s just a tad less efficient than the above reader-writer pattern.

这篇关于如何在Swift中处理种族条件读写的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆