Я новичок в gevents и greenlets. Я нашел хорошую документацию о том, как с ними работать, но ни одна из них не дала мне обоснования того, как и когда мне следует использовать гринлеты!
- В чем они действительно хороши?
- Стоит ли использовать их в прокси-сервере или нет?
- Почему не темы?
Я не уверен в том, как они могут обеспечить нам параллелизм, если в основном они являются совместными подпрограммами.
threading.Thread
фактически является потоком ОС со всеми ответвлениями. Так что на самом деле все не так просто. Кстати, у Jython нет GIL AFAIK, и PyPy тоже пытается от него избавиться.Ответы:
Greenlets обеспечивают параллелизм, но не параллелизм. Параллелизм - это когда код может работать независимо от другого кода. Параллелизм - это одновременное выполнение параллельного кода. Параллелизм особенно полезен, когда в пользовательском пространстве нужно проделать много работы, а это, как правило, загружает процессор. Параллелизм полезен для разделения проблем, позволяя планировать и управлять различными частями параллельно.
Greenlets действительно блестят в сетевом программировании, где взаимодействие с одним сокетом может происходить независимо от взаимодействия с другими сокетами. Это классический пример параллелизма. Поскольку каждый гринлет выполняется в собственном контексте, вы можете продолжать использовать синхронные API без потоковой передачи. Это хорошо, потому что потоки очень дороги с точки зрения виртуальной памяти и накладных расходов ядра, поэтому параллелизм, которого можно достичь с помощью потоков, значительно меньше. Кроме того, многопоточность в Python дороже и более ограничена, чем обычно, из-за GIL. Альтернативой параллелизму обычно являются такие проекты, как Twisted, libevent, libuv, node.js и т. Д., Где весь ваш код использует один и тот же контекст выполнения и регистрирует обработчики событий.
Это отличная идея - использовать гринлеты (с соответствующей сетевой поддержкой, например, через gevent) для написания прокси, поскольку ваша обработка запросов может выполняться независимо и должна быть написана как таковая.
Greenlets обеспечивают параллелизм по причинам, о которых я говорил ранее. Параллелизм - это не параллелизм. Скрывая регистрацию событий и выполняя планирование вызовов, которые обычно блокируют текущий поток, такие проекты, как gevent, раскрывают этот параллелизм, не требуя изменений в асинхронном API, и при значительно меньших затратах для вашей системы.
источник
import hashlib def checksum_md5(filename): md5 = hashlib.md5() with open(filename,'rb') as f: for chunk in iter(lambda: f.read(8192), b''): md5.update(chunk) return md5.digest()
Взяв ответ @Max и добавив к нему некоторую релевантность для масштабирования, вы можете увидеть разницу. Я добился этого, изменив URL-адреса для заполнения следующим образом:
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org'] URLS = [] for _ in range(10000): for url in URLS_base: URLS.append(url)
Мне пришлось отказаться от многопроцессорной версии, так как она упала до того, как у меня было 500; но при 10000 итерациях:
Using gevent it took: 3.756914 ----------- Using multi-threading it took: 15.797028
Итак, вы можете видеть, что есть значительная разница в вводе-выводе с использованием gevent
источник
Исправляя ответ @TemporalBeing выше, гринлеты не «быстрее» потоков, и создание 60000 потоков для решения проблемы параллелизма является неправильной техникой программирования, вместо этого подходит небольшой пул потоков. Вот более разумное сравнение (из моего сообщения на Reddit в ответ на людей, цитирующих это сообщение SO).
import gevent from gevent import socket as gsock import socket as sock import threading from datetime import datetime def timeit(fn, URLS): t1 = datetime.now() fn() t2 = datetime.now() print( "%s / %d hostnames, %s seconds" % ( fn.__name__, len(URLS), (t2 - t1).total_seconds() ) ) def run_gevent_without_a_timeout(): ip_numbers = [] def greenlet(domain_name): ip_numbers.append(gsock.gethostbyname(domain_name)) jobs = [gevent.spawn(greenlet, domain_name) for domain_name in URLS] gevent.joinall(jobs) assert len(ip_numbers) == len(URLS) def run_threads_correctly(): ip_numbers = [] def process(): while queue: try: domain_name = queue.pop() except IndexError: pass else: ip_numbers.append(sock.gethostbyname(domain_name)) threads = [threading.Thread(target=process) for i in range(50)] queue = list(URLS) for t in threads: t.start() for t in threads: t.join() assert len(ip_numbers) == len(URLS) URLS_base = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org'] for NUM in (5, 50, 500, 5000, 10000): URLS = [] for _ in range(NUM): for url in URLS_base: URLS.append(url) print("--------------------") timeit(run_gevent_without_a_timeout, URLS) timeit(run_threads_correctly, URLS)
Вот некоторые результаты:
-------------------- run_gevent_without_a_timeout / 30 hostnames, 0.044888 seconds run_threads_correctly / 30 hostnames, 0.019389 seconds -------------------- run_gevent_without_a_timeout / 300 hostnames, 0.186045 seconds run_threads_correctly / 300 hostnames, 0.153808 seconds -------------------- run_gevent_without_a_timeout / 3000 hostnames, 1.834089 seconds run_threads_correctly / 3000 hostnames, 1.569523 seconds -------------------- run_gevent_without_a_timeout / 30000 hostnames, 19.030259 seconds run_threads_correctly / 30000 hostnames, 15.163603 seconds -------------------- run_gevent_without_a_timeout / 60000 hostnames, 35.770358 seconds run_threads_correctly / 60000 hostnames, 29.864083 seconds
Все недоразумения по поводу неблокирования ввода-вывода с помощью Python заключаются в убеждении, что интерпретатор Python может выполнять работу по извлечению результатов из сокетов в большом масштабе быстрее, чем сами сетевые соединения могут возвращать ввод-вывод. Хотя это, безусловно, верно в некоторых случаях, это не так часто, как думают люди, потому что интерпретатор Python действительно очень медленный. В своем сообщении в блоге я проиллюстрирую некоторые графические профили, которые показывают, что даже для очень простых вещей, если вы имеете дело с четким и быстрым сетевым доступом к таким вещам, как базы данных или DNS-серверы, эти службы могут возвращаться намного быстрее, чем код Python. может поддерживать многие тысячи таких связей.
источник
Это достаточно интересно для анализа. Вот код для сравнения производительности гринлетов в сравнении с многопроцессорным пулом и многопоточностью:
import gevent from gevent import socket as gsock import socket as sock from multiprocessing import Pool from threading import Thread from datetime import datetime class IpGetter(Thread): def __init__(self, domain): Thread.__init__(self) self.domain = domain def run(self): self.ip = sock.gethostbyname(self.domain) if __name__ == "__main__": URLS = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org'] t1 = datetime.now() jobs = [gevent.spawn(gsock.gethostbyname, url) for url in URLS] gevent.joinall(jobs, timeout=2) t2 = datetime.now() print "Using gevent it took: %s" % (t2-t1).total_seconds() print "-----------" t1 = datetime.now() pool = Pool(len(URLS)) results = pool.map(sock.gethostbyname, URLS) t2 = datetime.now() pool.close() print "Using multiprocessing it took: %s" % (t2-t1).total_seconds() print "-----------" t1 = datetime.now() threads = [] for url in URLS: t = IpGetter(url) t.start() threads.append(t) for t in threads: t.join() t2 = datetime.now() print "Using multi-threading it took: %s" % (t2-t1).total_seconds()
вот результаты:
Using gevent it took: 0.083758 ----------- Using multiprocessing it took: 0.023633 ----------- Using multi-threading it took: 0.008327
Я думаю, что гринлет утверждает, что он не связан с GIL, в отличие от библиотеки многопоточности. Более того, в документе Greenlet говорится, что он предназначен для сетевых операций. Для операций, интенсивно использующих сеть, переключение потоков подходит, и вы можете видеть, что многопоточный подход работает довольно быстро. Также всегда предпочтительнее использовать официальные библиотеки python; Я попытался установить greenlet в Windows и столкнулся с проблемой зависимости dll, поэтому я провел этот тест на Linux vm. Всегда пытайтесь писать код в надежде, что он будет работать на любой машине.
источник
getsockbyname
кэширует результаты на уровне ОС (по крайней мере, на моей машине это происходит). При вызове на ранее неизвестном или просроченном DNS он фактически выполняет сетевой запрос, который может занять некоторое время. При вызове для имени хоста, которое недавно было разрешено, он вернет ответ намного быстрее. Следовательно, ваша методология измерения здесь ошибочна. Это объясняет ваши странные результаты - gevent на самом деле не может быть намного хуже, чем многопоточность - оба не совсем параллельны на уровне виртуальной машины.using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms