swift - 在 Swift 中,为什么以全局队列为目标的自定义串行队列不能同时执行?

标签 swift serialization concurrency queue grand-central-dispatch

这是我的第一个问题,所以请友善。
我很难理解 Swift 中队列目标的动态:

  1. 我了解到自定义队列继承其目标队列的行为。

  2. 在下面的示例中,concurrentQueue 的属性设置为 .concurrent

  3. 但是因为它的目标队列是 DispatchQueue.main,根据定义它是串行的,所以 concurrentQueue 会串行执行:

    let concurrentQueue = DispatchQueue(label: "concurrentQueue",
                                        attributes: .concurrent,
                                        target: DispatchQueue.main)
    
    concurrentQueue.async {
        for i in 1...5 {
            print(i)
        }
    }
    
    concurrentQueue.async {
        for i in 6...10 {
            print(i)
        }
    }
    

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
  4. 相反,如果我有一个自定义串行队列,该队列以全局队列为目标,并且根据定义是并发的,那么我的自定义串行队列期望同时执行。

  5. 但是,我的自定义串行队列仍在串行执行。这是为什么?

问题陈述:

  • 这里,serialQueue 没有定义使其成为串行队列的属性。

  • 因为它有一个并发队列 DispatchQueue.global(qos: .background) 作为其目标,所以我希望它能够同时执行。

  • 但是,输出仍然是串行的。

    let serialQueue = DispatchQueue(label: "serialQueue", target: DispatchQueue.global(qos: .background))
    
    serialQueue.async {
        for i in 1...5 {
            print(i)
        }
    }
    
    serialQueue.async {
        for i in 6...10 {
            print(i)
        }
    }
    

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
  • 最佳答案

    您描述的行为是正确的。

    • 如果您创建专用串行队列,无论其目标是什么,它将串行执行任务。目标队列的并发性质不会影响专用队列的串行性质。

    • 如果您创建私有(private)并发队列,并且目标是串行的,则您的私有(private)队列将受限于底层目标队列的串行行为。


    这就引出了一个问题:目标队列的目的是什么。

    一个很好的例子是“排除队列”。查看 WWDC 2017 视频 Modernizing Grand Central Dispatch Usage 。这个想法是,您可能有两个单独的串行队列,但您希望它们彼此独占运行,因此您将创建另一个串行队列,并将其用作其他两个队列的目标。它避免了不必要的上下文切换,确保跨多个队列的串行行为等。有关更多信息,请参阅该视频。


    松散相关,请参阅setTarget(_:) documentation ,它为目标提供了一些背景信息:

    The target queue defines where blocks run, but it doesn't change the semantics of the current queue. Blocks submitted to a serial queue still execute serially, even if the underlying target queue is concurrent. In addition, you can't create concurrency where none exists. If a queue and its target queue are both serial, submitting blocks to both queues doesn't cause those blocks to run concurrently. The blocks still run serially in the order the target queue receives them.


    顺便说一句,五次迭代可能会进行得足够快,以至于您可能看不到并行执行,即使使用并发队列也是如此:

    let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
    
    queue.async {
        for i in 1...5 {
            print(i)
        }
    }
    
    queue.async {
        for i in 6...10 {
            print(i)
        }
    }
    

    这通常仍然会误导您得出仍然存在串行执行的结论:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    它实际上是并发的,但第一个 block 在第二个 block 有机会开始之前完成!

    您可能需要引入一点延迟来体现并发执行。虽然您实际上不应该在实际代码中 Thread.sleep ,但出于说明目的,它会减慢速度足以演示并发执行:

    let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
    
    queue.async {
        for i in 1...5 {
            print(i)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    
    queue.async {
        for i in 6...10 {
            print(i)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    

    结果是:

    1
    6
    7
    2
    8
    3
    4
    9
    5
    10
    

    或者,如果您雄心勃勃,您可以使用“Instruments”»“Time Profiler”以及以下代码:

    import os.log
    
    let poi = OSLog(subsystem: "Demo", category: .pointsOfInterest)
    

    然后:

    let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
    
    queue.async {
        for i in 1...5 {
            let id = OSSignpostID(log: poi)
            os_signpost(.begin, log: poi, name: "first", signpostID: id, "Start %d", i)
            print(i)
            Thread.sleep(forTimeInterval: 1)
            os_signpost(.end, log: poi, name: "first", signpostID: id, "End %d", i)
        }
    }
    
    queue.async {
        for i in 6...10 {
            let id = OSSignpostID(log: poi)
            os_signpost(.begin, log: poi, name: "second", signpostID: id, "Start %d", i)
            print(i)
            Thread.sleep(forTimeInterval: 1)
            os_signpost(.end, log: poi, name: "second", signpostID: id, "End %d", i)
        }
    }
    

    如果使用Xcode的“Product”»“Profile”»“Time Profiler”,您可以直观地看到并行执行:

    enter image description here

    有关更多信息,请参阅 How to identify key events in Xcode Instruments?

    但是,再次避免Thread.sleep,但只需确保您在循环中做了足够的事情来体现并行执行。

    关于swift - 在 Swift 中,为什么以全局队列为目标的自定义串行队列不能同时执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76474005/

    相关文章:

    swift - 位置管理器不检测信标

    Swift 4 减少问题 : Cannot use mutating member on immutable value: function call returns immutable value

    ios - Interface Builder 不显示 segues 的 peek 和 pop 选项

    ios - 如何在iOS上使用Dispatch Queue问题?

    javascript - 如何序列化和恢复vuex存储状态?

    java - libgdx json 序列化器带有自定义读取器但默认写入器?

    python - HyperlinkedModelSerializer 在 django rest 框架中使用 auth.User 抛出 ImproperlyConfigured 错误

    java - Java 8 Collections并发处理

    mysql - 读取已提交和事务错误 1213 : Deadlock

    caching - Angular 2,共享服务http observable中的并发