This is my first question here, so please be nice.
I struggle to understand the dynamics around a queue's target in 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. However, my custom serial queue is still getting executed serially. Why is that?

PROBLEM STATEMENT:

  1. 在这里,serialQueue没有定义属性,这使它成为一个序列队列.

  2. 因为它有一个并发队列DispatchQueue.global(qos: .background)作为目标,所以我预计它会被并发执行.

  3. 然而,输出仍然是连续的.

    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
    

推荐答案

你所描述的行为是正确的.

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

  • 如果您创建专用并发队列,如果目标是串行的,则您的专用队列将被限制为基础目标队列的串行行为.


这就回避了目标队列的目的是什么的问题.

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


松散相关,请参阅setTarget(_:) documentation,其中提供了目标的一些背景:

目标队列定义块的运行位置,但它不会更改当前队列的语义.提交到串行队列的块仍然按顺序执行,即使基础目标队列是并发的.此外,您不能在不存在的地方创建并发.如果一个队列及其目标队列都是串行的,则向两个队列提交块不会导致这些块并发运行.这些块仍然以目标队列接收它们的顺序连续运行.


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

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

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

您可能需要引入一点延迟来显示并发执行.尽管您实际上永远不应该在真实代码中使用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

或者,如果你有雄心壮志,你可以使用"仪器"»"时间档案器"与以下代码:

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的"产品"、"配置文件"、"时间配置文件",就可以直观地看到并行执行的情况:

enter image description here

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

但是,同样,避免Thread.sleep,但只需确保您在循环中做了足够的工作来显示并行执行.

Swift相关问答推荐

解析SON数据的API调用有什么问题?

防止NavigationLink自行返回导航视图

如何使用快速宏用来自函数的关联值来初始化枚举?

Result类型的初始值设定项失败?

Swift ui 转换无法按预期工作

使用变量(而非固定)的字符串分量进行Swift正则表达式

除法中如果除以完全因数需要更长时间吗?

如何在闭包中使用构造 await sync

Swift Property Wrappers-Initialization 失败并显示无法将‘Double’类型的值转换为预期的参数类型

String(validatingUTF8:) 和 String(utf8String:) 之间有区别吗?

您可以在运行时访问 Picker 元素并更改它们的 colored颜色 吗

如何根据 SwiftUI 中的字体大小定义按钮的大小?

为什么 Swift Tuples 只能在元素个数小于等于 6 的情况下进行比较?

如何删除桥头而不出错?

SwiftUI 中的 .primary 和 .secondary colored颜色 是什么?

调整文本的字体大小以适应 UIButton

3D touch /强制touch 实现

自定义 Google 登录按钮 - iOS

SwiftUI:将多个 BindableObjects 放入环境

Swift performSegueWithIdentifier 不起作用