Greenlet Vs. Threads
Я новичок в gevents и greenlets. Я нашел хорошую документацию о том, как работать с ними, но никто не дал мне оправдания о том, как и когда я должен использовать greenlets!
- В чем они действительно хороши?
- это хорошая идея, чтобы использовать их в прокси-сервер или нет?
- почему нет темы?
Я не уверен в том, как они могут предоставить нам параллелизм, если они в основном являются совместными подпрограммами.
4 ответов:
Это обеспечить параллелизм, но не параллельность. Параллелизм - это когда код может выполняться независимо от другого кода. Параллелизм-это одновременное выполнение параллельного кода. Параллелизм особенно полезен, когда есть много работы, которую нужно сделать в пользовательском пространстве, и это, как правило, тяжелый процессорный материал. Параллелизм полезен для разделения проблем, позволяя планировать и управлять различными частями более легко параллельно.
на самом деле это блеск в сетевом программировании, где взаимодействие с одним сокетом может происходить независимо от взаимодействия с другими сокетами. Это классический пример параллелизма. Поскольку каждый greenlet работает в своем собственном контексте, вы можете продолжать использовать синхронные API без потоковой передачи. Это хорошо, потому что потоки очень дороги с точки зрения виртуальной памяти и накладных расходов ядра, поэтому параллелизм, который вы можете достичь с потоками, значительно меньше. Кроме того, резьба в Python стоит дороже и больше ограничено, чем обычно из-за Гил. Альтернативой параллелизму обычно являются такие проекты, как Twisted, libevent, libuv, node.js и т. д., где весь ваш код использует один и тот же контекст выполнения и регистрирует обработчики событий.
Это отличная идея использовать greenlets (с соответствующей сетевой поддержкой, например, через gevent) для написания прокси, так как ваша обработка запросов может выполняться независимо и должна быть написана как таковая.
Это обеспечить параллелизм для причины я приводил ранее. Параллелизм-это не параллелизм. Скрывая регистрацию событий и выполняя планирование для вас при вызовах, которые обычно блокируют текущий поток, такие проекты, как gevent, предоставляют этот параллелизм, не требуя изменения асинхронного API и значительно дешевле для вашей системы.
принимая ответ @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; но на 10 000 итераций:
Using gevent it took: 3.756914 ----------- Using multi-threading it took: 15.797028таким образом, вы можете видеть, что есть некоторая существенная разница в вводе/выводе с помощью gevent
это достаточно интересно для анализа. Вот код для сравнения производительности greenlets по сравнению с многопроцессорным пулом и многопоточностью:
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Я думаю, что greenlet утверждает, что он не связан с GIL в отличие от многопоточной библиотеки. Кроме того, Greenlet doc говорит, что он предназначен для сетевых операций. Для интенсивной работы сети переключение потоков отлично, и вы можете видеть, что подход многопоточности это довольно быстро. Также всегда предпочтительно использовать официальные библиотеки python; я попытался установить greenlet на windows и столкнулся с проблемой зависимости dll, поэтому я запустил этот тест на виртуальной машине linux. Всегда стараюсь писать код, с надеждой, что он работает на любой машине.
исправление для ответа @TemporalBeing выше, greenlets не "быстрее", чем потоки, и это неправильный метод программирования для порождения 60000 нити для решения проблемы параллелизма вместо этого подходит небольшой пул потоков. Вот более разумное сравнение (от моего reddit post в ответ на людей, цитирующих этот так пост).
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недоразумение у всех есть о неблокирующее IO с Python-это убеждение, что интерпретатор Python может заниматься работой по извлечению результатов из сокетов в большом масштабе быстрее, чем сами сетевые подключения могут возвращать IO. Хотя это, безусловно, верно в некоторых случаях, это не так часто, как люди думают, потому что интерпретатор Python действительно очень медленный. В моем сообщение в блоге здесь, я иллюстрирую некоторые графические профили, которые показывают, что даже для очень простых вещей, если вы имеете дело благодаря четкому и быстрому сетевому доступу к таким вещам, как базы данных или DNS-серверы, эти службы могут возвращаться намного быстрее, чем код Python может обслуживать многие тысячи этих соединений.
Comments