Параллельный сетевой клиент в Какао - PullRequest
4 голосов
/ 20 сентября 2009

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

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

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

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

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

EDIT:

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

Ответы [ 2 ]

1 голос
/ 25 сентября 2009

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

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

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

Сделайте все это в главном потоке.

0 голосов
/ 24 августа 2010

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

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

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

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

...