如何测量 DispatchQueue 背压

问题描述

我的程序有许多并行进程,每个进程都在自己的队列中。我希望能够可视化/测量队列的背压。

一种方法是计算进入和退出的每个块,但我确信 GCD 已经有了这些信息。有没有更好的方法来测量背压?

解决方法

没有用于查询 GCD 队列中待处理块数的 API。也就是说,由于您询问的是您“拥有”的队列,您可以以一种让您跟踪它的方式包装它。例如,您可以围绕 dispatch_[a]sync 制作自己的包装器,它会在您将一个块入队时增加一个计数器,并在该块完成时包装该块以减少计数器。这真的不难实现。 (dispatch_queue_get_specific 可能是一个不错的起点...)

FWIW,几年前,我建议您使用 NSOperationQueue,但即使 NSOperationQueue 已弃用其 operationCount 属性,因此可以公平地说 Apple 不太可能继续提供此功能,因此如果您确实需要此功能,那么自己实现它可能是您的最佳选择。

这可能不会立即对您有帮助,但是如果您需要测量 GCD 队列上的“背压”,您可能没有以正确的方式使用它们。如果您将在执行期间阻塞的工作项排入队列,那很糟糕,并且最终会导致线程饥饿。如果您将大量可能想要取消的工作项排入队列,比如用户更改了屏幕或其他内容,那么您应该为可取消的工作项设计一个模式(或使用 NSOperation)。我正在努力想出一个用例,它要求您能够测量无法用您自己的代码解决的“背压”。

,

如果您想可视化正在发生的事情,您可以使用 OSLog、发布 .begin.end 事件并在 Instruments 的“兴趣点”中观看。 (请参阅 WWDC 2019 Getting Started with Instruments。)

因此,导入 os.log

import os.log

然后创建一个日志:

private let pointsOfInterestLog = OSLog(subsystem: Bundle.main.bundleIdentifier!,category: .pointsOfInterest)

加入 100 个任务:

for i in 0..<100 {
    enqueueTask(i) {
        print("done \(i)")
    }
}

例程在分派任务之前向兴趣点日志发布 .begin 事件,并在任务实际开始时发布 .end 事件,例如

func enqueueTask(_ index: Int,completion: @escaping () -> Void) {
    let id = OSSignpostID(log: pointsOfInterestLog)
    os_signpost(.begin,log: pointsOfInterestLog,name: "backlog",signpostID: id,"queued %d",index)

    queue.async {
        os_signpost(.end,"started %d",index)
        ...
        completion()
    }
}

然后配置应用程序(使用 command+i 或“产品”»“配置文件”)并选择例如“时间配置文件”(其中包括“兴趣点”工具)。开始记录,您将看到积压工作的可视化表示:

enter image description here

(我将“兴趣点”扩大到足以显示所有 100 个积压的任务。)

这是一种可视化您的积压工作的方法。我必须承认,我通常使用“兴趣点”不是为了显示积压,而是显示这些任务实际运行的时间(即 .begin 分派任务实际开始运行时,.end 时分派任务完成)。或者我使用 Instruments 中的“线程”视图来查看我的工作线程是如何被使用的。或者我使用 Instruments 中的“CPU”视图来查看 CPU 的使用情况。

无论如何,如您所见,Instruments 可用于可视化您想要的任何时间范围,在这种情况下,是将任务添加到队列和开始运行之间的时间。