В Python потоки не ускоряют задачи, связанные с ограничениями процессора, из-за одной
детали реализации стандартной системы Python, которая называется Global Interpreter Lock
(GIL). Она предназначена для того, чтобы избежать потоковых проблем в интерпретаторе
Python, и действительно может замедлить многопоточную программу по сравнению с одно-
поточной или даже многопроцессорной версией.
Рассмотрим рекомендации для работы с Python.
Используйте потоки для задач, связанных с ограничениями ввода-вывода.
Используйте процессы, сетевые вычисления или события (которые мы рас-
смотрим в следующем разделе) для задач, связанных с ограничениями про-
цессора.
Зеленые потоки и gevent
Как вы уже видели, разработчики стремятся избежать медленных мест в програм-
мах, запуская их в отдельных потоках или процессах. Примером такого дизайна
является веб-сервер Apache.
Альтернативой этому подходу является программирование, основанное на со-
бытиях. Программа, основанная на событиях, запускает центральный цикл обра-
ботки событий, раздает задачи и повторяет цикл. Так устроен веб-сервер nginx, он
работает быстрее Apache.
Библиотека gevent основана на событиях и позволяет достичь следующего: вы
пишете обычный императивный код, и он волшебным образом превращается в со-
программы. Они похожи на генераторы, которые могут взаимодействовать друг
с другом и отслеживать свое текущее состояние. Библиотека gevent модифициру-
ет многие стандартные объекты Python вроде socket для того, чтобы использовать
Конкуренция
309
его механизм вместо блокирования. Это не сработает для кода надстроек Python,
который написан на С, например для некоторых драйверов баз данных.
На момент написания этой книги библиотека gevent не была полностью портирована на
Python 3, поэтому примеры кода используют инструменты Python 2 pip2 и python2.
Вы можете установить библиотеку gevent с помощью версии pip для Python 2:
$ pip2 install gevent
Так выглядит пример кода на сайте библиотеки gevent (http://www.gevent.org/).
Вы увидите функцию gethostbyname() класса socket в следующем разделе DNS. Эта
функция работает синхронно, поэтому вам придется подождать (возможно, много
секунд), пока она не получит имена серверов со всего мира, чтобы найти нужный
адрес. Но вы можете использовать версию gevent, чтобы искать несколько сайтов
независимо друг от друга. Сохраните этот файл как gevent_test.py:
import gevent
from gevent import socket
hosts = ['www.crappytaxidermy.com', 'www.walterpottertaxidermy.com',
'www.antique-taxidermy.com']
jobs = [gevent.spawn(gevent.socket.gethostbyname, host) for host in hosts]
gevent.joinall(jobs, timeout=5)
for job in jobs:
print(job.value)
В этом примере вы можете увидеть однострочный цикл for. Каждое имя хоста
по очереди передается в вызов gethostbyname(), но они могут быть запущены асин-
хронно благодаря версии функции gethostbyname() библиотеки gevent.
Запустите файл gevent_test.py с помощью Python 2, введя следующее: