Swift 数组与 DispatchQueue 同步
Swift Array Synchronization with DispatchQueue
如果从同一个线程(线程 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 { [=11=] == 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 { [=12=] == 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)
}
}
网络调用的完成处理程序可以更新 Session
的 state
属性。例如,用户选择 10 张图像并上传。完成后可能是失败也可能是成功。对于我们上传的每张图片,我们都会缓存资源 id
并在上传失败时将其删除。当所有图片上传失败时,我们更新状态.failed
。我们只关心上传的一张图片。当上传单张图片时,我们将状态更新为 .submitted
使用同步来确保与 FlowState
的线程安全交互是完全有效的。
几点观察:
您提供了两个备选方案:
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)
}
如果您还没有,我建议您在开发和测试期间打开 thread sanitizer (TSAN)。它可能会帮助您解决这些问题。
您没有显示 state
的任何额外读数,但如果有任何读数,请确保您也同步读数。与 state
的所有交互都必须同步。
一个微妙的问题:如果您要同步对 state
的访问,请确保将其设为 private
,这样就没有外部代码可以访问它(否则你可能会阻碍你确保线程安全交互的尝试)。您需要将所有读取和写入包装在您的同步机制中。
您可能也应该将同步队列设为私有,因为也不需要公开它。
如果从同一个线程(线程 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 { [=11=] == 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 { [=12=] == 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)
}
}
网络调用的完成处理程序可以更新 Session
的 state
属性。例如,用户选择 10 张图像并上传。完成后可能是失败也可能是成功。对于我们上传的每张图片,我们都会缓存资源 id
并在上传失败时将其删除。当所有图片上传失败时,我们更新状态.failed
。我们只关心上传的一张图片。当上传单张图片时,我们将状态更新为 .submitted
使用同步来确保与 FlowState
的线程安全交互是完全有效的。
几点观察:
您提供了两个备选方案:
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) }
- 线程 1 检索
如果您还没有,我建议您在开发和测试期间打开 thread sanitizer (TSAN)。它可能会帮助您解决这些问题。
您没有显示
state
的任何额外读数,但如果有任何读数,请确保您也同步读数。与state
的所有交互都必须同步。一个微妙的问题:如果您要同步对
state
的访问,请确保将其设为private
,这样就没有外部代码可以访问它(否则你可能会阻碍你确保线程安全交互的尝试)。您需要将所有读取和写入包装在您的同步机制中。您可能也应该将同步队列设为私有,因为也不需要公开它。