К настоящему времени мы узнали, как одновременно обслуживать несколько клиентов с помощью ветвления процессов и дочерних потоков, и рассмотрели библиотечный класс, инкапсулирующий обе эти схемы. В обоих подходах обработчики, обслуживающие клиентов, выполняются параллельно друг с другом и с главным циклом, продолжающим ожидать новые входящие запросы. Так как все эти задачи выполняются параллельно (то есть одновременно), сервер не блокируется при получении новых запросов или при выполнении продолжительных операций во время обслуживания клиентов.
Однако технически потоки и процессы на самом деле не выполняются одновременно, если только у вас на компьютере нет очень большого количества процессоров. На практике операционная система проделывает фокус — она делит вычислительную мощность процессора между всеми активными задачами, выполняя часть одной, затем часть другой и так далее. Кажется, что все задачи выполняются параллельно, но это происходит только потому, что операционная система переключается между выполнением разных задач так быстро, что обычно это незаметно. Такой процесс переключения между задачами, осуществляемый операционной системой, иногда называют кван то ва ни ем вре ме ни (ti me— slicing). Более общим его названием является муль ти п лек сиро ва ние (multiple xing).
При порождении потоков и процессов мы рассчитываем, что операционная система так будет жонглировать активными задачами, что ни одна из них не будет ущемляться в вычислительных ресурсах, особенно главный поток сервера. Однако нет никаких причин, по которым этим не мог бы заниматься также сценарий Python. Например, сценарий может разделять задачи на несколько этапов — выполнить этап одной задачи, затем другой и так далее, пока все они не будут завершены. Чтобы самостоятельно осуществлять мультиплексирование, сценарий должен лишь уметь распределять свое внимание среди нескольких активных задач.
Серверы могут с помощью этого приема осуществить еще один способ одновременной обработки клиентов, при котором не требуются ни потоки, ни ветвление. Мультиплексируя соединения клиентов и главного диспетчера с помощью системного вызова select, можно обслуживать клиентов и принимать новые соединения параллельно (или близко к тому, избегая задержек). Такие серверы иногда называются асин хрон ны- ми, поскольку они обслуживают клиентов импульсно, по мере их готовности к общению. В асинхронных серверах один главный цикл, выполняемый в одном процессе и потоке, решает каждый раз, которому из клиентов должно быть уделено внимание. Запросы клиентов и главный диспетчер получают небольшой квант внимания сервера, если они готовы к общению.
Основное волшебство при такой организации сервера обеспечивается вызовом select операционной системы, доступным в Python на всех основных платформах через стандартный модуль select. Приблизительно действие select заключается в том, что его просят следить за списком источников входных данных, выходных данных и исключительных ситуаций, а он сообщает, какие из источников готовы к обработке. Можно заставить его просто опрашивать все источники, чтобы находить те, которые готовы; ждать готовности источников в течение некоторого предельного времени или ждать неограниченное время готовности к обработке одного или нескольких источников.
При любом режиме использования select позволяет направлять внимание на сокеты, которые готовы к обмену данными, чтобы избежать блокирования при обращении к тем, которые не готовы. Это означает, что, когда источники, передаваемые вызову select, являются сокетами, можно быть уверенными, что такие методы сокетов, как accept, recv и send, не заблокируют (не остановят) сервер при применении к объектам, возвращаемым вызовом select. Благодаря этому сервер с единственным циклом, но использующий select, не застрянет при обслуживании какого-то одного клиента или в ожидании новых, в то время как остальные клиенты будут обделены его вниманием.
Поскольку такая разновидность серверов не требует запускать потоки или процессы, она может оказаться более эффективной, когда обслуживание клиентов занимает относительно короткое время. Однако при этом также требуется, чтобы обмен данными с клиентами выполнялся очень быстро. В противном случае возникает риск застрять в ожидании окончания диалога с каким-то определенным клиентом, если не предусмотреть использование потоков или ветвления процессов для выполнения продолжительных операций.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011