DispatchQueue с фиксированной шириной, отличной от FIFO

Я реализовал настраиваемую параллельную очередь поверх GCD, которая предлагает две дополнительные функции:

  1. Ограничьте максимальное количество одновременно выполняемых задач.
  2. Предоставьте вручную контроль над тем, какая задача из очереди запланирована к выполнению следующей.

An OperationQueue может, конечно, обратиться к первому, но не дает контроля над вторым. Однако возможность влиять на порядок, в котором очередь выполняет свою задачу, полезна для целей моего приложения (ожидание и задержка дорогостоящих задач, которые, вероятно, будут отменены), поэтому я попытался реализовать эту реализацию.

У меня несколько проблем:

  1. Тот факт, что это один предоставление класса два отдельные части функциональности кажутся красным флагом. Вероятно, было бы лучше реализовать это в двух отдельных типах, возможно, с одним подклассом другого. Но я не вижу прямого способа распутать это без чрезмерного усложнения вещей.

  2. Необходимость в трех очередях и семафоре, чтобы все координировать, — не самое лучшее. Я пытаюсь быть слишком умным?

  3. Просматривая некоторые из руководство libdispatch, особенно этот пост от сопровождающего, заставляет меня задуматься, является ли весь этот вариант использования чем-то вроде анти-паттерна (особенно в отношении использования семафоров для ожидания асинхронной работы). Это просто плохая идея — попытаться переоборудовать такую ​​функциональность в GCD?

Учитывая мои первоначальные требования (т.е. ограниченная пропускная способность и настраиваемый порядок планирования), разумен ли этот подход удаленно? Если да, то как это можно улучшить?

Заранее спасибо!

final class Queue {

    let count: Int

    private let block: (Range<Int>) -> Int

    private let semaphore: DispatchSemaphore

    private let objectQueue: DispatchQueue

    private let semaphoreQueue: DispatchQueue

    private let workItemQueue: DispatchQueue

    private var workItems = Array<DispatchWorkItem>()

    init(count: Int, qos: DispatchQoS = .default, block: @escaping (Range<Int>) -> Int) {
        self.count = count

        self.block = block

        self.semaphore = .init(value: count)

        self.objectQueue = .init(label: "object-queue", qos: qos)

        self.semaphoreQueue = .init(label: "semaphore-queue", qos: qos)

        self.workItemQueue = .init(label: "workitem-queue", qos: qos, attributes: [.concurrent])
    }

}

extension Queue {

    func async(execute work: @escaping () -> ()) {
        objectQueue.async { [self] in
            let workItem = DispatchWorkItem(flags: [.inheritQoS]) {
                work()

                semaphore.signal()
            }

            workItems.append(workItem)

            semaphoreQueue.async {
                semaphore.wait()

                objectQueue.async {
                    let index = block(workItems.indices)

                    let workItem = workItems.remove(at: index)

                    workItemQueue.async(execute: workItem)
                }
            }
        }
    }

    func async(execute workItem: DispatchWorkItem) {
        async { workItem.perform() }
    }

}

Редактировать

В block закрытие выбирает индекс следующего запланированного рабочего элемента. Его аргумент Range гарантированно не будет пустым.

Рабочие элементы находятся в том же порядке, в котором они были поставлены в очередь. block не предназначен для связи с конкретными рабочими элементами, а скорее для принятия решения между выбором тех, которые некоторое время ожидают выполнения, и тех, которые только что поступили в очередь.

Я использую его таким образом:

let count = max(1, ProcessInfo.processInfo.activeProcessorCount - 2)

let queue = Queue(count: count) { indices in
    let value = sqrt(.random(in: 0 ..< 1))

    let index = Int(value * Double(indices.count))

    return indices[index]
}

В этой конфигурации очередь выбирает рабочие элементы случайным образом, перекос в сторону более новых. Это может показаться нелогичным, но при работе с большим количеством дорогостоящих задач, которые могут быть отменены (в моем случае загрузка ресурсов для отображения в представлении коллекции во время прокрутки), это приводит к заметному повышению производительности.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *