Ограничены ли неблокирующие операции ввода-вывода в Perl одним потоком?Хороший дизайн?
-
21-08-2019 - |
Вопрос
Я пытаюсь разработать сервис, который содержит множество клиентских и серверных сокетов (серверную службу, а также клиентов, которые подключаются к управляемым компонентам и сохраняются), которые синхронно опрашиваются через IO::Select
.Идея состояла в том, чтобы обрабатывать потребности в вводе-выводе и / или обработке запросов, которые возникают через пулы рабочих потоков.
В shared
ключевое слово, которое делает данные доступными для обмена между потоками в Perl (threads::shared
) имеет свои ограничения - ссылки на дескрипторы не входят в число примитивов, которые можно сделать общими.
До того, как я понял, что дескрипторы и / или ссылки на дескрипторы не могут быть общими, план состоял в том, чтобы иметь select()
поток, который выполняет опрос, а затем помещает соответствующие дескрипторы в определенные ThreadQueue
s распределяются по пулу потоков, чтобы фактически выполнять чтение и запись.(Я, конечно, проектировал это так, чтобы внести изменения в фактические наборы дескрипторов, используемые select
было бы потокобезопасным и выполнялось бы только в одном потоке - том же, который выполняется select()
, и, следовательно, очевидно, никогда во время его работы.)
Не похоже, что это произойдет сейчас, потому что сами дескрипторы не могут быть общими, поэтому опрос, а также чтение и запись должны выполняться из одного потока.Есть ли какое-нибудь обходное решение для этого?Я имею в виду разложение фактических системных вызовов по потокам;очевидно, что существуют способы использования очередей и буферов для получения данных в других потоках и фактической отправки в другие.
Одна из проблем, которая возникает в связи с этой ситуацией, заключается в том, что я должен дать select()
время ожидания и ожидайте, что оно будет достаточно высоким, чтобы не вызывать никаких проблем с опросом довольно большого набора дескрипторов, и в то же время достаточно низким, чтобы не вводить слишком большую задержку в мой цикл событий синхронизации - хотя я понимаю, что если в процессе опроса обнаружено фактическое членство в наборе ввода-вывода, select()
вернется рано, что частично смягчает проблему.Я бы предпочел иметь какой-нибудь способ проснуться select()
из другого потока, но поскольку дескрипторы не могут быть общими, я не могу легко придумать способ сделать это и не вижу смысла в этом;о чем будет знать другой поток, когда будет уместно разбудить select()
в любом случае?
Если обходного пути нет, то какой хороший шаблон проектирования для этого типа сервиса в Perl?У меня есть требование к довольно высокой масштабируемости и параллельному вводу-выводу, и по этой причине я пошел по неблокирующему маршруту, а не просто создавал потоки для каждого прослушивающего сокета и / или клиентского и / или серверного процесса, как это обычно делают многие люди, использующие языки более высокого уровня в наши дни при работе с сокетами - похоже, это своего рода стандартная практика в Java, и, похоже, никого не волнует java.nio.*
за пределами узкой сферы системно-ориентированного программирования.Может быть, это только мое впечатление.В любом случае, я не хочу делать это таким образом.
Итак, с точки зрения опытного системного программиста Perl, как этот материал должен быть организован?Монолитный поток ввода-вывода + чистые рабочие потоки (не связанные с вводом-выводом) + множество очередей?Какой-то хитроумный взлом?Есть какие-нибудь подводные камни потокобезопасности, на которые следует обратить внимание помимо того, что я уже перечислил?Есть ли Лучший Способ?У меня есть большой опыт создания архитектуры такого рода программ на C, но не с использованием идиом Perl или характеристик времени выполнения.
Редактировать:P.S.Мне определенно пришло в голову, что, возможно, программу с такими требованиями к производительности и таким дизайном просто не следует писать на Perl.Но я вижу огромное количество очень сложных сервисов, созданных на Perl, так что я не уверен в этом.
Решение
Заключая в квадратные скобки несколько ваших более масштабных вопросов по дизайну, я могу предложить несколько подходов к совместному использованию дескрипторов файлов в потоках perl.
Один может пройти $client
к процедуре запуска потока или просто ссылаться на нее в новом потоке:
$client = $server_socket->accept();
threads->new(\&handle_client, $client);
async { handle_client($client) };
# $client will be closed only when all threads' references
# to it pass out of scope.
Для Thread::Queue
дизайн, можно enqueue()
лежащий в основе fd:
$q->enqueue( POSIX::dup(fileno $client) );
# we dup(2) so that $client may safely go out of scope,
# closing its underlying fd but not the duplicate thereof
async {
my $client = IO::Handle->new_from_fd( $q->dequeue, "r+" );
handle_client($client);
};
Или можно просто использовать исключительно fds и битовую векторную форму Perl select
.