在 OperationQueue 中设置背压(或替代 API,例如 PromiseKit、Combine Framework)
Setting backpressure in OperationQueue (or alternative API, e.g. PromiseKit, Combine Framework)
我在处理许多图像的管道中有 2 个步骤:
- 第 1 步:在本地加载(或下载)图像(IO 绑定)
- 第 2 步:运行 机器学习模型(CPU/GPU/计算绑定/单线程,因为模型很大)。如何限制存储在内存中(从第 1 步开始)排队等待第 2 步的图像数量。这在 Reactive 编程中称为 backpressure。
如果没有背压,第 1 步的所有工作可能会堆积起来,导致仅用于打开图像的高内存使用率。
我想我可以使用一个信号量(例如 5),它大致表示我愿意为步骤 1(5 张图片)提供的内存量。我想这会使我的 5 个后台线程阻塞,这可能是一件坏事? (这是一个严肃的问题:阻塞后台线程是不是很糟糕,因为它会消耗资源。)
如果您使用的是 Combine,flatMap
可以提供背压。 FlatMap
为它收到的每个值创建一个发布者,但当它达到指定的最大未完成发布者数量时施加背压。
这是一个简化的例子。假设你有以下功能:
func loadImage(url: URL) -> AnyPublisher<UIImage, Error> {
// ...
}
func doImageProcessing(image: UIImage) -> AnyPublisher<Void, Error> {
// ...
}
let urls: [URL] = [...] // many image URLs
let processing = urls.publisher
.flatMap(maxPublishers: .max(5)) { url in
loadImage(url: url)
.flatMap { uiImage in
doImageProcessing(image: uiImage)
}
}
在上面的示例中,它将加载 5 张图像,并开始处理它们。第 6 张图像将在前面的图像之一完成处理后开始加载。
如果您真的想使用 OperationQueue,只需将队列的 maxConcurrentOperationCount
设置为 5 即可防止同时启动超过 5 个操作。
我在处理许多图像的管道中有 2 个步骤:
- 第 1 步:在本地加载(或下载)图像(IO 绑定)
- 第 2 步:运行 机器学习模型(CPU/GPU/计算绑定/单线程,因为模型很大)。如何限制存储在内存中(从第 1 步开始)排队等待第 2 步的图像数量。这在 Reactive 编程中称为 backpressure。
如果没有背压,第 1 步的所有工作可能会堆积起来,导致仅用于打开图像的高内存使用率。
我想我可以使用一个信号量(例如 5),它大致表示我愿意为步骤 1(5 张图片)提供的内存量。我想这会使我的 5 个后台线程阻塞,这可能是一件坏事? (这是一个严肃的问题:阻塞后台线程是不是很糟糕,因为它会消耗资源。)
如果您使用的是 Combine,flatMap
可以提供背压。 FlatMap
为它收到的每个值创建一个发布者,但当它达到指定的最大未完成发布者数量时施加背压。
这是一个简化的例子。假设你有以下功能:
func loadImage(url: URL) -> AnyPublisher<UIImage, Error> {
// ...
}
func doImageProcessing(image: UIImage) -> AnyPublisher<Void, Error> {
// ...
}
let urls: [URL] = [...] // many image URLs
let processing = urls.publisher
.flatMap(maxPublishers: .max(5)) { url in
loadImage(url: url)
.flatMap { uiImage in
doImageProcessing(image: uiImage)
}
}
在上面的示例中,它将加载 5 张图像,并开始处理它们。第 6 张图像将在前面的图像之一完成处理后开始加载。
如果您真的想使用 OperationQueue,只需将队列的 maxConcurrentOperationCount
设置为 5 即可防止同时启动超过 5 个操作。