【问题标题】:Swift Array Synchronization with DispatchQueue使用 DispatchQueue 实现 Swift 数组同步
【发布时间】:2020-03-24 04:20:59
【问题描述】:

如果从同一个线程(线程 1 主线程)调用 uploadFailed(for id: String)uploadSuccess()updateOnStart(_ id: String),我知道我们不需要同步队列。如果每次上传时从不同的线程调用函数怎么办。我在哪里确保同步?是上传和状态还是只是状态?

enum FlowState {
 case started(uploads: [String])
 case submitted
 case failed
}

class Session {
 var state: FlowState
 let syncQueue: DispatchQueue = .init(label: "Image Upload Sync Queue",
                                      qos: .userInitiated,
                                      attributes: [],
                                      autoreleaseFrequency: .workItem)

 init(state: FlowState) {
   self.state = state
 }

 mutating func updateOnStart(_ id: String) {
  guard case .started(var uploads) = state else {
    return
  }

  uploads.append(id)

  state = .started(uploads)
}

  mutating func uploadFailed(for id: String) {
   guard case .started(var uploads) = state else {
    return
   }

   uploads.removeAll { $0 == id }

   if uploads.isEmpty {
      state = .failed
   } else {
      state = .started(uploads)
   }
 }

 mutating func uploadSuccess() {
  state = .submitted
 }

}

我们是否同步uploads 数组操作和如下状态?

syncQueue.sync {
  uploads.append(id)

  state = .started(uploads)
}


syncQueue.sync {
  uploads.removeAll { $0 == id }

   if uploads.isEmpty {
      state = .failed
   } else {
      state = .started(uploads)
   }
}

syncQueue.sync {
  state = .started(uploads)
}

syncQueue.sync {
  if uploads.isEmpty {
    state = .failed
  } else {
    state = .started(uploads)
  }
}

网络调用的完成处理程序可以更新Sessionstate 属性。例如,用户选择 10 张图像并上传。完成后,它可能是失败或成功。对于我们上传的每张图片,我们都会缓存资源id,如果上传失败,我们会将其删除。当所有图片上传失败时,我们更新状态.failed。我们只关心上传一张图片。当单张图片上传时,我们将状态更新为.submitted

【问题讨论】:

  • 您所说的“是上传和状态还是只是状态?”是什么意思?
  • 您能提供您具体用例的背景吗?你想做什么?
  • @JoshWolff 我用更多信息更新了问题
  • “如果每次上传时从不同的线程调用函数怎么办” 也许这似乎是一种肤浅的反应,但我的第一个想法是确保不会发生这种情况是你的工作。 .?
  • 回调尚未创建,其他人将处理异步同时上传的上传。这只是一个后备

标签: ios swift synchronized dispatch-queue


【解决方案1】:

使用同步来确保与FlowState 的线程安全交互是完全有效的。

一些观察:

  1. 您提出了两种选择:

    syncQueue.sync {
        uploads.append(id)
    
        self.state = .started(uploads)
    }
    

    或者

    syncQueue.sync {
        state = .started(uploads)
    }
    

    这些都不对。如果线程 1 和线程 2 同时调用这个例程怎么办?考虑:

    • 线程 1 检索到 uploads
    • 线程 2 检索到相同的uploads
    • 然后线程 1 进入它的 sync 块并将一条记录附加到它的本地副本,保存它,然后离开它的 sync 闭包;
    • 然后线程 2 进入它的 sync 块并将不同的记录附加到它自己的本地副本(没有由线程 1 添加的记录),保存它,然后离开它的 sync 闭包。
       

    在这种情况下,您将丢失附加的线程 1。

    因此,您需要采用第三种更广泛的同步机制,将检索、附加和存储uploads 的整个过程封装在一个同步机制中:

    syncQueue.sync {
        guard case .started(var uploads) = self.state else {
            return
        }
    
        uploads.append(id)
    
        state = .started(uploads)
    }
    
  2. 如果您还没有,我建议您在开发和测试期间打开thread sanitizer (TSAN)。它可能会帮助您发现这些问题。

  3. 您没有显示任何对 state 的额外读取,但如果有任何其他读取,请确保您也同步读取。与state 的所有交互都必须同步。

  4. 一个微妙的问题:如果您要同步对 state 的访问,请确保将其设置为 private,以便没有外部代码可以访问它(否则您可能会阻碍您确保线程安全的交互)。您需要将所有读取和写入都包含在同步机制中。

    您可能也应该将同步队列设为私有,因为也不需要公开它。

【讨论】:

  • 谢谢罗伯。那真的很有帮助。 public private(set) var state: FlowState 状态只能公开阅读
  • 无意冒犯,但是当您可能正在写作时,您不能让其他线程阅读此内容。您不想公开任何未同步的访问,即使只是读取。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-04
  • 1970-01-01
  • 2018-08-28
相关资源
最近更新 更多