python eventlet详解
正真工作才发现很懒,没这么多时间写文,毕竟小白,参照大神写的,不喜勿喷
1.eventlet是什么
eventlet - 具有WSGI支持的异步框架
eventlet是python库函数,一个是处理和网络相关的,另一个可以通过协程实现并发
可以实现'并发'(绿色线程),非阻塞
对Python库函数改写,支持协程
绿色线程和普通线程区别
1. 绿色线程几乎没有开销,不用像保留普通线程一样保留“绿色线程”,每一个网络连接对应至少一个“绿色线程”;
2. 绿色线程需要人为的设置使其互相让渡CPU控制权,而不是抢占。绿色线程既能够共享数据结构,又不需要显式的互斥控制,因为只有当一个绿色线程让出了控制权后其他的绿色线程才能访问彼此共享的数据结构。
下图是eventlet中协程、hub、线程、进程之间的关系:
2.eventlet依赖的库
1.greenlet
greenlet库是其并发的基础,eventlet库简单的对其进行封装之后,就构成了GreenTread。
2.select.epoll
select库中的epoll则是默认的网络通信模型。正式由于这两个库的相对独立性,可以从两方面学习eventlet库。
3.协程
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。多核CPU使用多进程+协程
4.迭代器
4.1可迭代对象(iterable)实现了__iter__()方法的对象,通过调用iter()方法可以获得一个迭代器(Iterator)。
4.2迭代器(iterator)是实现了iterator.__iter__()和iterator.__next__()方法的对象
4.3iterator是消耗型的,即每一个值被使用过后,就消失了
4.4for ... in ...语句的工作原理--即是一个生成器把所有数全推出来
4.5.yield用法,先返回了yield表达式的值,然后中断,等待next()调用再执行.
4.6.generator其实有第2种调用方法(恢复执行),即通过send(value)方法将value作为yield表达式的当前值,要确保,generator是在yield处被暂停了,才能传值,可以用next()方法
5.GreenLet
6.几个主要API的理解
6.1 greenlet
1.每个协程都有自己的私有stack及局部变量;
2.同一时间内只有一个协程在运行,故无须对某些共享变量加锁;
3.协程之间的执行顺序,完成由程序来控制;
6.2 GreenThread
eventlet中对greenlet进行了简单的封装,GreenThread的调度通过hub来实现
6.3 GreenPool
该模块提供对 greenthread 池的支持。
class eventlet.greenpool.GreenPool(size=1000)
类主要方法:
1. free()
2. imap(function, *iterables)
3. resize(new_size)
4. running()
5. spawn(function, *args, **kwargs)
6. spawn_n(function, *args, **kwargs)
7. starmap(function, iterable)
8. waitall()
9. waiting()
1.
free()
返回当前对象中可用的greenthreads。
如果为 0 或更少,那么 spawn() 和 spawn_n() 将会阻塞调用 greenthread 直到有新的可用的 greenthread 为止。
至于为什么此处可能返回负值,请查看3. resize()
2.
imap(function, *iterables)
效果等同于 itertools.imap() ,在并发和内存使用上等同于 starmap() 。
例如,可以非常方便地对文件做一些操作:
def worker(line):
return do_something(line)
pool = GreenPool()
for result in pool.imap(worker, open("filename", 'r')):
print(result)
3.
resize(new_size)
改变当前允许同时工作的 greenthreads 最大数量
如果当前有多于 new_size 的 greenthreads 处于工作中,它们可以完成自己的执行,只不过此时不许任何的新 greenthreads 被分配。只有当足够数量的 greenthreads 完成自己的工作,然后工作中的 greenthreads 总数低于 new_size 时,新的 greenthreads 才能被分配。在此之前,free() 的返回值将会使负的。
4.
running()
返回当前池子中正在执行任务的 greenthreads 。
5.
spawn(function, *args, **kwargs)
从当前的池子中孵化一个可用的greenthread,在这个 greenthread 中执行 function ,参数 *args, **kwargs 为传给 function 的参数。返回一个 GreenThread 对象,这个对象执行着 function ,可以通过该 GreenThread 对象获取 function 的返回值。
如果当前池子中没有空余的 greenthread ,那么该方法阻塞直到有新的可用的 greenthreads 被释放。
该函数可以重用, function 可以调用同一个 GreenPool 对象的 spawn 方法,不用担心死锁。
6.
spawn_n(function, *args, **kwargs)
创建一个 greenthread 来运行 function,效果等同于 spawn()。 只不过这个函数返回 None,即丢弃 function 的返回值。
7.
starmap(function, iterable)
等同于 itertools.starmap(),除了对于可迭代对象中的每一个元素,都会在一个 greenthread 里面执行 func 。 并发的上限由池子的容量限制。在实际的操作中, starmap() 消耗的内存与池子的容量成比例,从而格外适合遍历特别长的输入列表。
8.
waitall()
等待池子中的所有 greenthreads 完成工作。
9.
waiting()
返回当前等待孵化的 greenthreads 数。
6.4 GreenPile(绿色线程池,可以有效的控制并发)
pile = eventlet.GreenPile(pool)
# 执行函数,把值放进去,函数可以重用, function 可以调用同一个 GreenPool 对象的 spawn 方法,不用担心死锁。
pile.spawn(func,value)
一般
1. next()
等待下一个结果,挂起当前的 greenthread 直到结果可用为止。 当没有更多的结果时,抛出 StopIteration 异常。
2. spawn(func, *args, **kw)
在它自己的 greenthread 中运行 func,结果储存在 GreenPile 对象中,可以迭代该对象获取这些结果。
7.例子
7.1 爬虫
import eventlet
from eventlet.green import urllib2
urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
"https://wiki.secondlife.com/w/images/secondlife.jpg",
"http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"]
def fetch(url):
return urllib2.urlopen(url).read()
pool = eventlet.GreenPool()
for body in pool.imap(fetch, urls):
print("got body", len(body))
第2行引入绿化后的 urllib2,除了使用绿化后的套接字外,与原有的标准库完全相同。
第11行创建一个绿色线程池,此处缺省容量为1000,线程池可以控制并发,限制内存消耗的上限;
第12行遍历并行调用函数 fetch 后的结果,imap 可以并行调用函数 fetch ,返回结果的先后顺序和执行的先后顺序相同。
这个例子的关键就在于客户端起了若干的绿色线程,并行收集网络爬取的结果,同时由于绿色线程池加了内存帽,也不会因为url列表过大而消耗过多的内存。
7.2 简单socket服务器
import eventlet
def handle(client):
while True:
c = client.recv(1)
if not c: break
client.sendall(c)
server = eventlet.listen(('0.0.0.0', 6000))
pool = eventlet.GreenPool(10000)
while True:
new_sock, address = server.accept()
pool.spawn_n(handle, new_sock)
server = eventlet.listen(('0.0.0.0', 6000)) 一句创建一个监听套接字;
pool = eventlet.GreenPool(10000) 一句创建一个绿色线程池,最多可以容纳10000个客户端连接;
new_sock, address = server.accept() 一句很特殊,由于这里创建的服务器套接字是经过绿化的,所以当多个连接到来时在accept()这里不会阻塞,而是并行接收
pool.spawn_n(handle, new_sock) 一句为每一个客户端创建一个绿色线程,该绿色线程不在乎回调函数 handle 的执行结果,也就是完全将客户端套接字交给回调 handle 处理。
7.3
Feed 挖掘机
该用例下,一个服务端同时也是另一个服务的客户端,比如代理等,这里 GreenPile 就发挥作用了。
下面的例子中,服务端从客户端接收 POST 请求,请求中包括含有 RSS feed 的URL,服务端并发地到 feed 服务器那里取回所有的 feed 然后将他们的标题返回给客户端:
import eventlet
feedparser = eventlet.import_patched('feedparser')
pool = eventlet.GreenPool()
def fetch_title(url):
d = feedparser.parse(url)
return d.feed.get('title', '')
def app(environ, start_response):
pile = eventlet.GreenPile(pool)
for url in environ['wsgi.input'].readlines():
pile.spawn(fetch_title, url)
titles = '\n'.join(pile)
start_response('200 OK', [('Content-type', 'text/plain')])
return [titles]
使用绿色线程池的好处是控制并发,如果没有这个并发控制的话,客户端可能会让服务端在 feed 服务器那里起很多的连接,导致服务端被feed服务器给 ban 掉。
总结:
1. Greenthread Spawn(产生greenthread绿色线程)
spawn(func, *args, **kwargs)
2. Greenthread Control(控制greenthread)
eventlet.GreenPool;eventlet.GreenPile;eventlet.Queue
3. Network Convenience Functions(和网络相关的函数)
绿化socket