Swift DispatchQueue 全局变量和主变量

Swift DispatchQueue global and main in variable

我有 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。但最好的方法是什么,因为是背景和主要。

我可能是错的,但我认为不同的队列没有太大区别, 你还得“等”到 完成,例如:

var myItemsTotal: Int = 0

getMyFirstItem() { val1 in
    getMySecondtItem() { val2 in
        getMyThirdItem() { val3 in
            myItemsTotal = val1 + val2 + val3
            print(" myItemsTotal: \(myItemsTotal)")
        }
    }
}

关键问题是保证线程安全。例如,下面是不是线程安全的:

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 reader-writer、actor 等)。但我从串行队列示例开始观察,实际上,任何串行队列都可以完成同样的事情。许多人使用主队列(这是一个串行队列)来进行这种对性能影响可以忽略不计的琐碎同步,例如在这个例子中。

例如,因此可以重构 getMySecondItem 以在主队列上调用其完成处理程序,就像 getMyFirstItemgetMyThirdItem 已经做的那样。或者,如果您不能这样做,您可以简单地让 getMySecondItem 调用者分派需要同步到主队列的代码:

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 reader-writer 模式。

关键是永远不要在没有同步的情况下改变来自多个线程的变量。


上面我提到了你需要知道三个异步任务什么时候完成。您可以使用 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 { [=13=] }
        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