Swift DispatchQueue: последовательный или параллельный - PullRequest
1 голос
/ 20 июня 2020

В моем приложении я должен распаковать несколько файлов в фоновом режиме за одно и то же время. Какой код приводит к параллельному выполнению массива compressedFiles в нескольких потоках:

for file in compressedFiles {
  DispatchQueue.global(qos: .userInteractive).async {
    let work = DispatchGroup()
    work.enter()
    file.decompress()
    work.leave()
  }
}

или:

DispatchQueue.global(qos: .userInteractive).async {
  for file in compressedFiles {
    let work = DispatchGroup()
    work.enter()
    file.decompress()
    work.leave()
  }
}

Кроме того, как воспользоваться преимуществами DispatchGroup class, если я хочу получать уведомление о завершении процесса распаковки одного из файлов? Куда поставить wait () и notify ()?

Спасибо.

Ответы [ 2 ]

1 голос
/ 21 июня 2020

Ваш второй пример будет запускать их последовательно. Он выполняет единую рассылку, выполняя их одну за другой. В первом примере они будут запускаться параллельно, отправляя каждый в другой рабочий поток. К сожалению, ни одна из них не использует группу отправки правильно.

Что касается группы отправки, вы должны определить ее перед l oop и enter, прежде чем звонить по номеру async. Но ручной вызов enter и leave необходим только в том случае, если вы вызываете асинхронный процесс из вызова async. Но учитывая, что decompress, вероятно, является синхронным процессом, вы можете просто указать группу в async, и он обо всем позаботится за вас:

let group = DispatchGroup()

for file in compressedFiles {
    DispatchQueue.global(qos: .userInteractive).async(group: group) {
        file.decompress()
    }
}

group.notify(queue: .main) {
    // all done
}

Но вместо того, чтобы беспокоиться о logi c группы диспетчеризации, в параллельном примере есть более глубокая проблема. В частности, он страдает от взрыва потока, когда он может превышать количество доступных ядер на вашем процессоре. Хуже того, если у вас было много файлов для распаковки, вы даже можете превысить ограниченное количество рабочих потоков, которые GCD имеет в своем пуле. И когда это произойдет, это может предотвратить запуск чего-либо еще на GCD для этого QoS. Вместо этого вы хотите запускать его параллельно, но вы хотите ограничить его разумной степенью параллелизма, сохраняя при этом параллелизм, чтобы избежать истощения ресурсов для других задач.

Если вы хотите, чтобы он работал параллельно , но и избежать взрыва резьбы, часто можно достичь concurrentPerform. Это обеспечивает максимальный параллелизм, поддерживаемый ЦП, но предотвращает проблемы, которые могут возникнуть в результате взрыва потока:

DispatchQueue.global(qos: .userInitiated).async {
    DispatchQueue.concurrentPerform(iterations: compressedFiles.count) { index in
        compressedFiles[index].decompress()
    }
    
    DispatchQueue.main.async {
        // all done
    }
}

Это ограничит параллелизм до максимума, разрешенного ядрами вашего устройства. Это также устраняет необходимость в диспетчерской группе.

В качестве альтернативы, если вы хотите наслаждаться параллелизмом, но с более низкой степенью параллелизма (например, чтобы оставить некоторые ядра доступными для других задач, чтобы минимизировать пиковое использование памяти и т. Д. c.), Вы можете использовать очереди операций и maxConcurrentOperationCount:

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4  // a max of 4 decompress tasks at a time

let completion = BlockOperation {
    // all done
}

for file in compressedFiles {
    let operation = BlockOperation {
        file.decompress()
    }
    completion.addDependency(operation)
    queue.addOperation(operation)
}

OperationQueue.main.addOperation(completion)

Или, как указывает Мэтт, в iOS 13 (или macOS 10.15) и более поздних версиях вы можете сделать:

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4

for file in compressedFiles {
    queue.addOperation {
        file.decompress()
    }
}

queue.addBarrierBlock {
    DispatchQueue.main.async {
        // all done
    }
}
0 голосов
/ 20 июня 2020

Группа отправки должна находиться за пределами l oop, а каждая enter должна находиться внутри l oop, но вне потока, содержащего leave. Но тогда код весь также должен находиться в отдельной очереди отправки, поскольку вы не можете заблокировать (ждать) в основной очереди.

let queue = DispatchQueue(label:"myqueue")
queue.async {
    let work = DispatchGroup()
    for file in compressedFiles {
        work.enter()
        DispatchQueue.global(qos: .userInteractive).async {
            file.decompress()
            work.leave()
        }
    }
    work.notify... // get on main thread here?
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...