如何处理Swift中的竞赛条件读取/写入问题? [英] How to handle Race Condition Read/Write Problem in Swift?

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

问题描述

  1. 我在 Raywenderlich 帖子示例

private letcurrentPhotoQueue = DispatchQueue(标签:"com.raywenderlich.GooglyPuff.photoQueue",属性:.concurrent)

private let concurrentPhotoQueue = DispatchQueue(label: "com.raywenderlich.GooglyPuff.photoQueue", attributes: .concurrent)

在其中完成写操作的地方

Where write operations is done in

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& amp;20个正确

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

  1. 解决比赛状况的不同方法有哪些

  • 调度屏障
  • 信号量
  • 如果我不正确,则信号量和障碍两个不同吗?当我尝试解决 _accountBalance 的读/写时,它对ATM和Bank都是负数,这与调度障碍是不对的,但是对于 Semaphore ,我正在等待正确的结果线程任务完成

    If I am not correct Semaphores and barrier two different ?. When I try to solve read/write for _accountBalance it gets negative for ATM and Bank which is wrong with dispatch barrier but with Semaphore I am getting correct result as we wait for the thread task to complete

    protocol Banking {
        func withDrawAmount(amount: Double) throws;
    }
    
    enum WithdrawalError : Error {
        case inSufficientAccountBalance
    }
    
    private var _accountBalance = 30000.00
    
    private let threadSafeCountQueue = DispatchQueue(label: "com.thread.safe", attributes: .concurrent)
    
     
    public var accountBalance: Double {
          get {
             threadSafeCountQueue.sync {
              return _accountBalance
            }
        } set {
    
            threadSafeCountQueue.async(flags: .barrier) {
                _accountBalance = newValue
            }
        }
    }
    
    struct Atm : Banking {
    
        func withDrawAmount(amount: Double) throws {
            debugPrint("inside atm")
    
            guard accountBalance > amount else { throw WithdrawalError.inSufficientAccountBalance }
    
            // Intentional pause : ATM doing some calculation before it can dispense money
            Thread.sleep(forTimeInterval: Double.random(in: 1...3))
            accountBalance -= amount
        }
    
        func printMessage() {
            debugPrint("ATM withdrawal successful, new account balance = \(accountBalance)")
        }
    }
    
    struct Bank : Banking {
    
        func withDrawAmount(amount: Double) throws {
            debugPrint("inside bank")
    
            guard accountBalance > amount else { throw WithdrawalError.inSufficientAccountBalance }
    
            // Intentional pause : Bank person counting the money before handing it over
            Thread.sleep(forTimeInterval: Double.random(in: 1...3))
            accountBalance -= amount
        }
    
        func printMessage() {
            debugPrint("Bank withdrawal successful, new account balance = \(accountBalance)")
        }
    }
    

    用于ATM和银行的钱款分配任务.这对信号量是正确的,但只有调度屏障才能达到预期的效果.

    let queue = DispatchQueue(label: "semaphoreDemo", qos: .utility, attributes: .concurrent)
        
        let semaphore = DispatchSemaphore(value: 1)
        
        queue.async {
            // Money withdrawal from ATM
            do {
                semaphore.wait()
                let atm = Atm()
                try atm.withDrawAmount(amount: 10000) // withdraw 10K
                atm.printMessage()
                semaphore.signal()
        
            } catch WithdrawalError.inSufficientAccountBalance {
                semaphore.signal()
                debugPrint("ATM withdrawal failure: The account balance is less than the amount you want to withdraw, transaction cancelled")
            }
            catch {
                semaphore.signal()
                debugPrint("Error")
            }
        }
        
        queue.async {
            // Money withdrawal from Bank
            do {
                semaphore.wait()
                let bank = Bank()
                try bank.withDrawAmount(amount: 25000) // withdraw 25K
                bank.printMessage()
                semaphore.signal()
        
            } catch WithdrawalError.inSufficientAccountBalance  {
                semaphore.signal()
                debugPrint("Bank withdrawal failure: The account balance is less than the amount you want to withdraw, transaction cancelled")
            }
            catch{
                semaphore.signal()
                debugPrint("Error")
            }
        }
    

    推荐答案

    您问:

    为什么不使用屏障...来完成读取??

    Why is Read not done with 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. It’s the whole motivating idea behind reader-writer pattern, to allow concurrent reads.

    因此,您可以将屏障与读取"一起使用(它仍然是线程安全的),但是如果碰巧同时调用了多个读取"请求,则会对性能产生不必要的负面影响时间.如果两个读取"操作可以彼此同时发生,为什么不让它们呢?除非绝对必要,否则请不要使用障碍(降低性能).

    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天全站免登陆