问题描述
我的程序有许多并行进程,每个进程都在自己的队列中。我希望能够可视化/测量队列的背压。
一种方法是计算进入和退出的每个块,但我确信 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 或“产品”»“配置文件”)并选择例如“时间配置文件”(其中包括“兴趣点”工具)。开始记录,您将看到积压工作的可视化表示:
(我将“兴趣点”扩大到足以显示所有 100 个积压的任务。)
这是一种可视化您的积压工作的方法。我必须承认,我通常使用“兴趣点”不是为了显示积压,而是显示这些任务实际运行的时间(即 .begin
分派任务实际开始运行时,.end
时分派任务完成)。或者我使用 Instruments 中的“线程”视图来查看我的工作线程是如何被使用的。或者我使用 Instruments 中的“CPU”视图来查看 CPU 的使用情况。
无论如何,如您所见,Instruments 可用于可视化您想要的任何时间范围,在这种情况下,是将任务添加到队列和开始运行之间的时间。