Greenlet Vs. Threads



Я новичок в gevents и greenlets. Я нашел хорошую документацию о том, как работать с ними, но никто не дал мне оправдания о том, как и когда я должен использовать greenlets!




  • В чем они действительно хороши?

  • это хорошая идея, чтобы использовать их в прокси-сервер или нет?

  • почему нет темы?


Я не уверен в том, как они могут предоставить нам параллелизм, если они в основном являются совместными подпрограммами.

654   4  

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

    Ничего не найдено.