Вопрос

Я пытаюсь придумать в голове лучший способ структурировать приложение Cocoa, которое по сути является менеджером параллельных загрузок.Есть сервер, с которым общается приложение, пользователь составляет большой список вещей, которые нужно извлечь, и приложение обрабатывает этот список.(Он не использует HTTP или FTP, поэтому я не могу использовать систему загрузки URL-адресов;Я буду говорить о сокетных соединениях.)

По сути, это классическая модель «производитель-потребитель».Хитрость в том, что количество потребителей фиксировано и они постоянны.Сервер устанавливает строгое ограничение на количество одновременных подключений, которые могут быть открыты (хотя обычно не менее двух), а открытие новых подключений обходится дорого, поэтому в идеальном мире одни и те же N подключений открыты на протяжении всего времени существования приложения.

Одним из способов решения этой проблемы может быть создание N потоков, каждый из которых будет «владеть» соединением, и ожидать очереди запросов, блокируясь, если она пуста.Поскольку количество соединений никогда не будет огромным, это вполне разумно с точки зрения реальных издержек системы.Но концептуально кажется, что Cocoa должна предложить более элегантное решение.

Кажется, я мог бы использовать NSOperationQueue, и позвони setMaxConcurrentOperationCount: с количеством подключений.Затем я просто бросаю запросы на загрузку в эту очередь.Но я не уверен в таком случае, как управлять самими соединениями.(Просто поместите их в стопку и положитесь на очередь, чтобы убедиться, что я не переборчу/не превышу?Добавьте семафор отправки вместе со стеком?)

Теперь, когда мы в дивном новом мире Гранд Центральная диспетчерская, открывает ли это какие-либо другие способы решения этой проблемы?На первый взгляд, это не так, поскольку флагманская способность GCD динамически масштабировать параллелизм (упомянутая в рекомендациях Apple по Изменение реализаций производитель-потребитель) на самом деле мне не помогает.Но я лишь поверхностно прочитал об этом.

РЕДАКТИРОВАТЬ:

В случае, если это имеет значение:да, я планирую использовать API асинхронных/неблокирующих сокетов для фактической связи с сервером.Таким образом, сам ввод-вывод не обязательно должен осуществляться в отдельном потоке(ах).Меня просто интересует механика постановки работы в очередь и (безопасного) распределения ее по соединениям по мере их появления.

Это было полезно?

Решение 2

Ради потомков, после некоторого обсуждения в другом месте, я думаю, что решение, которое я бы принял для этого, в основном следующее:

  • Иметь очередь ожидающих операций загрузки, изначально пустую.
  • Иметь набор, содержащий все открытые соединения, изначально пустой.
  • Иметь изменяемый массив (на самом деле очередь) простаивающих открытых соединений, изначально пустой.
  • Когда пользователь добавляет запрос на загрузку:
    • Если массив неактивных соединений не пуст, удалите одно и назначьте ему загрузку.
    • Если неработающих подключений нет, но общее количество подключений не достигло предела, откройте новое подключение, добавьте его в набор и назначьте ему загрузку.
    • В противном случае отложите загрузку на потом.
  • Когда загрузка завершится:Если есть запросы в очереди, Dequeue One и дайте их подключению;в противном случае добавьте соединение в список ожидания.

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

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

Другие советы

Если вы используете неблокирующие вызовы CFSocket для ввода-вывода, я согласен, все это должно происходить в основном потоке, позволяя ОС решать проблемы параллелизма, поскольку вы просто копируете данные и на самом деле не выполняете никаких вычислений.

Помимо этого, похоже, что единственная работа, которую должно выполнять ваше приложение, — это поддерживать очередь элементов для загрузки.Когда любая из передач завершена, обратный вызов CFSocket может инициировать передачу следующего элемента в очереди.(Если очередь пуста, уменьшите количество подключений, а если что-то будет добавлено в пустую очередь, начните новую передачу.) Я не понимаю, зачем вам для этого нужно несколько потоков.

Возможно, вы упустили что-то важное, но, судя по вашему описанию, приложение привязано к вводу-выводу, а не к ЦП, поэтому все элементы параллелизма просто будут создавать более сложный код с минимальным влиянием на производительность.

Делайте все это в основном потоке.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top