我是gevents和greenlets的新手。我找到了一些关于如何使用它们的好文档,但没有一个能让我证明我应该如何以及何时使用greenlets!
我不确定的是,如果它们基本上是共同惯例,它们如何为我们提供并发性。
答案 0 :(得分:175)
Greenlets提供并发性,但不并行性。并发是指代码可以独立于其他代码运行。并行性是同时执行并发代码。当在用户空间中进行大量工作时,并行性特别有用,而且通常是CPU占用大量的东西。并发对于分解问题很有用,可以更容易地并行地调度和管理不同的部分。
Greenlets确实在网络编程中大放异彩,其中与一个套接字的交互可以独立于与其他套接字的交互而发生。这是并发的典型示例。由于每个greenlet都在其自己的上下文中运行,因此您可以继续使用同步API而无需进行线程化。这很好,因为线程在虚拟内存和内核开销方面非常昂贵,因此使用线程实现的并发性要小得多。此外,由于GIL,Python中的线程比平时更昂贵且更有限。并发的替代方案通常是像Twisted,libevent,libuv,node.js等项目,其中所有代码共享相同的执行上下文,并注册事件处理程序。
使用greenlet(通过gevent提供适当的网络支持)来编写代理是一个很好的主意,因为您对请求的处理能够独立执行并且应该这样写。
Greenlets提供并发性的原因我之前给出了。并发不是并行性。通过隐藏事件注册并在通常会阻塞当前线程的调用上执行调度,像gevent这样的项目会暴露这种并发性,而不需要更改到异步API,并且系统的成本要低得多。
答案 1 :(得分:18)
采用@ 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
在I / O方面存在一些显着差异答案 2 :(得分:8)
这很有趣,可以分析。 下面是一个代码来比较greenlet与多处理池和多线程的性能:
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的官方图书馆也总是无法预料;我尝试在Windows上安装greenlet并遇到dll依赖问题所以我在linux vm上运行了这个测试。 Alway尝试编写代码,希望它可以在任何机器上运行。
答案 3 :(得分:4)
更正上面的@TemporalBeing的答案,greenlets的速度不比线程“快”,并且产生 60000个线程解决并发问题是不正确的编程技术,因此线程池很小而是适当的。这是一个比较合理的比较(根据我对我的reddit post的回应,引用了此类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进行非阻塞IO的误解是认为Python解释器可以比网络连接本身返回IO更快地参与从套接字检索结果的工作。尽管在某些情况下这确实是正确的,但事实并非如人们想象的那么频繁,因为Python解释器的确非常慢。在我的blog post here中,我演示了一些图形配置文件,这些配置文件显示即使对于非常简单的事情,如果您要处理对数据库或DNS服务器等事物的快速便捷的网络访问,则这些服务的返回速度可能比Python代码可以参与其中的数千个连接。