Greenlet比。 主题(Greenlet Vs. Threads)

2019-07-22 11:02发布

我是新来gevents和greenlets。 我发现如何与他们合作的一些好的文档,但没有给我理由,我应该如何以及何时使用greenlets!

  • 什么是他们最擅长?
  • 它是在一个代理服务器或不使用它们一个好主意?
  • 为什么不线程?

什么我不知道的是,他们如何能为我们提供的并发性,如果他们基本上协同例程。

Answer 1:

Greenlets提供并发但不能并行。 并发时的代码可以独立于其他代码的运行。 并行是并发代码的同时执行。 并行是非常有用的,当有在用户空间做了很多工作,这是典型的CPU-沉重的东西。 并发为分裂开的问题,从而使不同的部分将被调度且并行管理更容易地有用。

Greenlets大放异彩的网络编程,其中有一个插座的互动可以与其他插座相互作用的独立发生。 这是并发性的一个典型的例子。 因为每个greenlet在自己的上下文中运行,您可以继续使用同步的API,不必穿线。 这是一件好事,因为线程都在虚拟内存和内核开销方面非常昂贵,所以你可以用线程实现并发显著少。 另外,在Python线程是更昂贵的并且比由于GIL通常更受限制。 替代并发,通常的项目,如双绞线,libevent的,libuv,node.js的等在那里所有的代码共享相同的执行上下文,并注册事件处理程序。

这是一个很好的主意,用greenlets(在适当的网络支持,如通过GEVENT)编写的代理,为您的请求的处理都能够独立执行,应该写成这样。

Greenlets对于我刚才所提出的理由提供并发性。 并发不能并行。 隐瞒事件报名和通常会阻塞当前线程调用你的调度,像GEVENT项目公开此并行,而不需要改变一个异步API,并在您的系统成本显著少。

  • 并发不能并行
  • 线程与进程
  • 多处理与线程
  • GIL与CPython的


Answer 2:

以@ Max的答案,并增加了一些相关性就缩放,你可以看到其中的差别。 我通过改变如下待填充的网址,实现了这个:

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在I / O部分显著差异



Answer 3:

这是足够有趣的分析。 这里是一个比较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医生说,这是为网络操作。 对于网络密集型操作,线程切换是很好,你可以看到,多线程的方法是相当快的。 此外,它总是prefeerable使用Python的官方图书馆; 我试图在Windows上安装greenlet等我跑在Linux VM本测试中遇到一个dll依赖性问题。 送花儿给人尝试写一个代码,它运行在任何机器上的希望。



Answer 4:

校正@TemporalBeing的回答以上,greenlets比线程不是‘更快’,这是一个不正确的编程技术产卵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

的误解每个人都有约非阻塞IO与Python是信仰,Python解释器可以参加到大规模,从检索结果的插座比网络连接本身可以返回IO更快的工作。 虽然这是在某些情况下肯定是真的,这不是真的几乎一样经常像人们想象的,因为Python解释器是真的,真的很慢。 在我的博客文章在这里 ,我说明了一些图形化的配置文件是显示,即使是很简单的事情,如果你正在处理清脆快速的网络访问之类的数据库或DNS服务器,这些服务可以回来了很多比Python代码快可以参加到成千上万的连接。



文章来源: Greenlet Vs. Threads