§ Много активный соединений -> много процессов -> большой расход памяти. § Переключение контекста исполнения – дорого. § GIL. Проблемы использования потоков в Python. Блокирующий I/O Модели prefork, process-per-request
только когда доступны. § Пока одни соединения “ждут”, можно обрабатывать другие. Достаточно одного процесса (потока). § Относительно небольшой расход памяти, нет переключения контекста. § Программу приходится реализовывать в виде обработчиков на события I/O. Бумеранг код.
client.get('api.pandadoc.com/token', callback=_on_fetched) def _on_fetched(reponse): // process response and save result to db token = parse (response) db = AsyncDatabaseClient() db.save_token(token, callback=_on_saved) def _on_saved(status): // status string print 'Status: ' + str(status) Писать callbacks утомительно :(
client.get('api.pandadoc.com/token’) // process response and save result to db token = parse (response) db = DatabaseClient() status = db.save_token(token) // status string print 'Status: ' + str(status) § Синхронный код писать и читать проще. § Большинство стандартных библиотек написано в синхронном стиле § Но, приходиться использовать блокирующий I/O
функции, исполнение которых можно приостановить, а потом снова продолжить. § Работают на уровне пользователя внутри одного или нескольких потоков OC. § Для исполнения микропотоков нужен планировщик. § Дешевы в плане потребления памяти и переключения контекста.
потоках? § Когда микропоток хочет выполнить I/O он отдает управление планировщику. § Когда I/O для данного микропотока становится доступным, планировщик возобновляет работу микропотока. Таким образом callbacks заменяются на переключения между микропотоками.
передать исполнение другим. Существует также преемптивная или вытесняющая многозадачность – поток вытесняется планировщиком после определённого количества выполненных инструкций или по истечении определённого времени.
появились генераторы (PEP 342). Что бы передать исполнение можно использовать yield (send/next чтобы возобновить) Недостатки: § Кооперация с yield слишком явно, приходиться думать самому, когда передавать исполнение. § Генераторы не сохраняют весь стек во время остановки – yield должен быть всегда на самом верху. § Циклические вызовы внутри генераторов не работают. Больше тут: http://dabeaz.com/coroutines/
объект greenlet. § Кооперация посредством вызова метода greenlet.switch. § greenlet – это “выжимка” из Stackless Python. Официальная документация: http://greenlet.readthedocs.org/
сам цикл полностью в C коде. § Libevent используется длительное время и хорошо себя зарекомендовала (Chromium, Memcached). § Предоставляет встраиваемый HTTP-сервер – evhttp. § Имеет API для работы с DNS – evdns. Libev – будет использована в следующих версиях gevent, как более стабильная альтернатива (http://blog.gevent.org/2011/04/28/libev-and-libevent )
работает в отдельном гринлете, который называется хаб (hub). § Хаб запускается неявно и только при необходимости. § Кооперация гринлетов происходить только через хаб § Гринлет может переключиться только на хаб. § Гринлет может получить управления только через хаб.
гринлет должен: § Отправить запрос на I/O в цикл обработки событий. § Переключиться на хаб. § Хаб запускает цикл обработки событий и при необходимости передает управление другим гринлетам. § …… § Когда сокет готов выполнить I/O (не заблокировав поток), хаб возобновляет выполнение нашего гринлета в точке останова.
post), handler): acceptor = socket.socket(socket.AF_INET, socket.STREAM) acceptor.bind((host, port)) while True: client, address = acceptor.accept() spawn(handler, client, address) def handle(sock, address): f = sock.makefile() while True: line = f.readline() if not line: break f.write(line) f.flush() - Управление передается циклу libevent - Порождаем гиринлет на каждое клиентское соединение
позволяет “патчить” следующие модули: § socket § ssl § os § time § select § thread and trheading from gevent import monkey monkey.patch_socket() monkey.patch_all() … http://en.wikipedia.org/wiki/Monkey_patch
Pymongo, SQLAlchemy, boto, … Так же gunicorn, Falsk, Celery, Sentry, Django, … Motor для Tornado: http://emptysqua.re/blog/motor-internals-how-i- asynchronized-a-synchronous-library/
gevent.reinit(). § Библиотеку можно использовать только в одном потоке ОС – ограничение libevent 1.4. § Не масштабируется на многоядерных системах. Лечится использованием гибридной модели (gunicorn.org) § Библиотеки которые не используют socket блокируют интерпретатор полностью – можно выполнять их в отдельном потоке ОС. § Циклические ссылки в гринлетах могут привести к утечке памяти из-за проблем со сборщиком мусора (специфика реализации greenlet) § Вызов функции (callback) в питоне обойдется дешевле чем переключение гринлета. § Потеря или затирание исключений (нужно очень что-то сломать J).