我有 3 个这样的函数:
func getMyFirstItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
func getMySecondtItem(complete: @escaping (Int) -> Void) {
DispatchQueue.global(qos:.background).async {
complete(10)
}
}
func getMyThirdItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
我有一个变量:
var myItemsTotal: Int = 0
我想知道如何对项目求和,在本例中是 10 10 10 得到 30。但是最好的方法是什么,因为是背景和主要。
最佳答案
关键问题是保证线程安全。例如,以下内容不是线程安全的:
func addUpValuesNotThreadSafe() {
var total = 0
getMyFirstItem { value in
total += value // on main thread
}
getMySecondItem { value in
total += value // on some GCD worker thread!!!
}
getMyThirdItem { value in
total += value // on main thread
}
...
}
可以通过不允许这些任务并行运行来解决此问题,但您会失去异步进程及其提供的并发性的所有好处。
不用说,当您允许它们并行运行时,您可能会添加一些机制(例如调度组)来知道所有这些异步任务何时完成。但我不想使这个示例复杂化,而是将重点放在线程安全问题上。 (我稍后将在本答案中展示如何使用调度组。)
无论如何,如果您有从多个线程调用的闭包,则在不添加一些同步的情况下不得增加相同的total
。您可以添加与串行调度队列的同步,例如:
func addUpValues() {
var total = 0
let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".synchronized")
getMyFirstItem { value in
queue.async {
total += value // on serial queue
}
}
getMySecondItem { value in
queue.async {
total += value // on serial queue
}
}
getMyThirdItem { value in
queue.async {
total += value // on serial queue
}
}
...
}
有多种替代同步机制(锁、GCD 读写器、actor
等)。但我从串行队列示例开始,观察到实际上任何串行队列都会完成同样的事情。许多人使用主队列(这是一个串行队列)进行这种琐碎的同步,其性能影响可以忽略不计,例如本例。
例如,因此可以重构 getMySecondItem
以在主队列上调用其完成处理程序,就像 getMyFirstItem
和 getMyThirdItem
已经做的那样。或者,如果您无法做到这一点,您可以简单地让 getMySecondItem
调用者分派(dispatch)需要同步到主队列的代码:
func addUpValues() {
var total = 0
getMyFirstItem { value in
total += value // on main thread
}
getMySecondItem { value in
DispatchQueue.main.async {
total += value // now on main thread, too
}
}
getMyThirdItem { value in
total += value // on main thread
}
// ...
}
这也是线程安全的。这就是为什么许多库将确保在主线程上调用其所有完成处理程序,因为它可以最大限度地减少应用程序开发人员手动同步值所需的时间。
虽然我已经说明了如何使用串行调度队列进行同步,但还有多种替代方案。例如,可以使用锁或 GCD 读写器模式。
关键是,在没有同步的情况下,永远不应该从多个线程改变变量。
上面我提到你需要知道三个异步任务何时完成。您可以使用 DispatchGroup
,例如:
func addUpValues(complete: @escaping (Int) -> Void) {
let total = Synchronized(0)
let group = DispatchGroup()
group.enter()
getMyFirstItem { first in
total.synchronized { value in
value += first
}
group.leave()
}
group.enter()
getMySecondItem { second in
total.synchronized { value in
value += second
}
group.leave()
}
group.enter()
getMyThirdItem { third in
total.synchronized { value in
value += third
}
group.leave()
}
group.notify(queue: .main) {
let value = total.synchronized { $0 }
complete(value)
}
}
在此示例中,我从 addUpValues
中提取了同步详细信息:
class Synchronized<T> {
private var value: T
private let lock = NSLock()
init(_ value: T) {
self.value = value
}
func synchronized<U>(block: (inout T) throws -> U) rethrows -> U {
lock.lock()
defer { lock.unlock() }
return try block(&value)
}
}
显然,使用您想要的任何同步机制(例如,GCD 或 os_unfair_lock
或其他)。
但这个想法是,在 GCD 世界中,调度组可以在一系列异步任务完成时通知您。
我知道这是一个 GCD 问题,但为了完整起见,Swift 并发 async
-await
模式使大部分内容变得毫无意义。
func getMyFirstItem() async -> Int {
return 10
}
func getMySecondItem() async -> Int {
await Task.detached(priority: .background) {
return 10
}.value
}
func getMyThirdItem() async -> Int {
return 10
}
func addUpValues() {
Task {
async let value1 = getMyFirstItem()
async let value2 = getMySecondItem()
async let value3 = getMyThirdItem()
let total = await value1 + value2 + value3
print(total)
}
}
或者,如果您的异步方法正在更新某些共享属性,您将使用 actor
来同步访问。请参阅Protect mutable state with Swift actors .
关于ios - Swift DispatchQueue 全局和主变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70492426/