[501]python eventlet

eventlet是python库函数,一个是处理和网络相关的,另一个可以通过协程实现并发。所谓并发,就是开启了多个greenthread(绿色线程),并且对这些greenthread进行管理,以实现非阻塞式的I/O。eventlet为了实现“绿色线程”,竟然对python的和网络相关的几个标准库函数进行了改写,并且可以以补丁(patch)的方式导入到程序中,因为python的库函数只支持普通的线程,而不支持协程,eventlet称之为“绿化”。

一、什么是协程

1、协程是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他的协同程序共享全局变量和其他大部分东西

2、线程与协程主要区别:

一个多线程的程序可以同时运行几个线程,而协同程序需要彼此协作运行。

一个具有多个协程的程序在任何时刻只能运行一个协程,并且正在执行的协程只会在自己显示的挂起时,执行才会暂停。

二、协程的好处

1、每个协程都有自己私有的栈和局部变量

2、同一时间只有一个协程在运行,无需对全局变量进行加锁

3、顺序可控,完全是由程序控制执行的顺序。而通常多线程一旦启动,运行时序是没法预测的,因此通常会给测试所有的程序带来问题。

三、eventlet是什么,用来做什么

1、eventlet是Python库函数,一个是处理和网络相关的,另一个可以通过协程实现并发

2、在eventlet里,把“协程”叫做greenthread(绿色线程)。

所谓并发,就是开启了多个greenthread(绿色线程),并且对这些greenthread进行管理,以实现非阻塞式的I/O。

比如说用eventlet可以很方便的写一个性能很好的web服务器,或者是一个效率很高的网页爬虫,这都归功于eventlet的"绿色线程",以及对“绿色线程”的管理机制。eventlet为了实现“绿色线程”,竟然对python的和网络相关的几个标准库函数进行了改写,并且可以以补丁(patch)的方式导入到程序中,因为python的库函数只支持普通的线程,而不支持协程,eventlet称之为“绿化”。

本文首先讲述GreenLet,GreenThread,然后说明eventlet中几个常用的类

GreenLet

greenlet是一个称为协程(coroutine)的东西,有下面几个特点。
 
1.每个协程都有自己的私有stack及局部变量; 
2.同一时间内只有一个协程在运行,故无须对某些共享变量加锁; 
3.协程之间的执行顺序,完成由程序来控制;

总之,协程就是运行在一个线程内的伪并发方式,最终只有一个协程在运行,然后程序来控制执行的顺序。可以看下面的例子来理解上面的意思。

import greenlet

def test1(n):
    print("test1:",n)
    gr2.switch(32)
    print("test1: over")

def test2(n):
    print("test2:",n )
    gr1.switch(23)
    print("test2: over")

greenlet = greenlet.greenlet
current = greenlet.getcurrent()
gr1 = greenlet(test1,current)
gr2 = greenlet(test2,current)
gr1.switch(2)

这段程序的执行结果如下:

test1: 2  
test2: 32  
test1: over  

整个程序的过程很直白,首先创建两个协程,创建的过程传入了要执行的函数和父greenlet,然后调用其中的一个协程的switch函数,并且传递参数进去,就开始执行test1,然后到了gr2.switch(32)语句,切换到test2函数来,最后又切换回去。最终test1运行结束,回到父greenlet中,执行结束。这个过程就是始终只有一个协程在运行,函数的执行流由程序自己来控制。这个过程在上面的链接中描述的更加具体。

参考资料 : http://greenlet.readthedocs.org/en/latest/

GreenThread

那么在eventlet中对greenlet进行了简单的封装,就成了GreenThread,并且上面的程序还会引来一个问题,如果我们想要写一个协程,那到底该如何来控制函数的执行过程了,如果协程多了,控制岂不是很复杂了。带着这个问题来看eventlet的实现。

在eventLet中,GreenThread的调度是通过hub来实现的。hub是EventLet的时间循环,用来调度IO事件和GreenThread。 
Hub有不同版本的实现,如epolls,poll,selects,pyevent版,不同版本的性能特性不同,(具体见http://eventlet.net/doc/hubs.html#eventlet.hubs.use_hub)。可使用eventlet.hubs.use_hub(hub=None)来配置使用的hub,其中,传入的参数为选用的hub的名字。

hub是线程局部类实例,即每个线程都有一个hub实现,用来管理该线程中的co-rountine的调度。你可以为每个线程选用合适的hub(这样很麻烦,通常是:你为主线程选用一个hub,其他线程使用默认的hub)

hub工作原理:hub自己有一个GreenLet,作为MainLoop.当某个运行中的coroutine需要IO操作时,则该co-routine在hub中注册一个监听器(hub知道何时该co-routine wake up)。如果有其他可运行的co-routine,则hub MainLoop 切换到这些co-routine(via get_hub().switch())。如果这些co-routime完成了或是也需要IO,则由切换到hub main Loop中。这样,保证每个co-rountine都能被调度。

hub MAINLOOP 在第一个IO操作时启动。这种lazy模式使得不需要显示的调用dispatch函数。

eventlet官方的文档(http://eventlet.net/doc/basic_usage.html)

GreenPool

该模块提供对 greenthread 池的支持。

greenthread 池提供了一定数量的备用 greenthread ,有效限制了孵化 greenthread 过多导致的内存不足,当池子中没有足够的空闲 greenthread 时,孵化过程被暂停,只有当先前工作中的 greenthread 完成当前工作,才能为下一个任务做孵化准备。

class eventlet.greenpool.GreenPool(size=1000)

类主要方法:

    1. free()
    1. imap(function, *iterables)
    1. resize(new_size)
    1. running()
    1. spawn(function, *args, **kwargs)
    1. spawn_n(function, *args, **kwargs)
    1. starmap(function, iterable)
    1. waitall()
    1. 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 数

GreenPile

class eventlet.greenpool.GreenPile(size_or_pool=1000) 
在它内部维护了一个GreenPool对象和一个Queue对象。这个GreenPool对象可以是从外部传递进来的,也可以是在类内部创建的,GreenPool对象主要是用来创建绿色线程的,即在GreenPile内部调用了GreenPool.spawn()方法。而Queue对象则是用来保spawn()方法的返回值的,即Queue中保存的是GreenThread对象。并且它还实现了next()方法,也就意味着GreenPile对象具有了迭代器的性质。所以如果我们要对绿色线程的返回值进行操作的话,用这个类是再好不过的了。

Event

class eventlet.event.Event

Event:在同一个线程中的协程通信的机制(an arbitrary number of coroutines can wait for one event from another)。Event和Queue类型,可看成只能容纳一条消息的Queue,但是和Queue不同的是: 
(1)调用send() 不会取消对当前greenthread的调度; 
(2)对一个event实例,只能调用一次send函数。如果你需要发送两条消息,则需要两个event对象。

一个使用实例:

import eventlet

evt = eventlet.event.Event()
def baz(b):
    evt.send(b + 1)

_ = eventlet.spawn_n(baz, 3)
evt.wait()

函数说明:

  1. ready() :判断一个Event对象有没有发出过事件,如果调用 wait() 会立即返回一个事件结果,那么此处就返回真值。该方法用来避免需要等待一段时间才会发生的事件。例如,你可以将一堆事件放到一个Python列表中,然后重复地遍历他们,这是就可以调用 ready() 直到其中的一个事件返回True,然后就可以立刻调用 wait() 来获取它了。
  2. send(result=None, exc=None) :发送一个事件,可唤醒其他wait的协程。调用该方法的协程会立刻返回(不会阻塞)
import eventlet
evt = eventlet.event.Event()
def waiter():
    print('about to wait')
    result = evt.wait()
    print('waited for {0}'.format(result))
_ = eventlet.spawn(waiter)#about to wait
eventlet.sleep(0)
evt.send('a')#waited for a
eventlet.sleep(0)

需要注意的是:不同对同一个event实例调用多次send函数,这会导致抛出异常

>>> evt.send('whoops')
Traceback (most recent call last):
...
AssertionError: Trying to re-send() an already-triggered event.

send_exception(*args) :作用类似于 send() 方法,只不过向等待者发送的是一个异常。如果单个异常对象被传进来,它会在 wait() 方法被调用的时候重新抛出,生成一个新的堆栈轨迹。

>>> from eventlet import event
>>> evt = event.Event()
>>> evt.send_exception(RuntimeError())
>>> evt.wait()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "eventlet/event.py", line 120, in wait
    current.throw(*self._exc)
RuntimeError

如果需要完整地保留堆栈轨迹,必须传入整个 sys.exc_info() 元组。

>>> import sys
>>> evt = event.Event()
>>> try:
...     raise RuntimeError()
... except RuntimeError:
...     evt.send_exception(*sys.exc_info())
...
>>> evt.wait()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "eventlet/event.py", line 120, in wait
    current.throw(*self._exc)
  File "<stdin>", line 2, in <module>
RuntimeError

此时会在Event对象内部存储一个 traceback 对象,这可能会导致循环引用。详见 sys.exc_info() 的文档。

wait() :等待直到另一个协程调用 send() 。返回其他协程传递给 send() 方法的值。

import eventlet

evt = eventlet.event.Event()
def wait_on():
    retval = evt.wait()
    print("waited for {0}".format(retval))
_ = eventlet.spawn(wait_on)
evt.send('result')#waited for result
eventlet.sleep(0)

举例

1、网络爬虫,用一个绿色线程池获取urls的信息。用到绿色线程池和imap()函数。

import eventlet,urllib.request
# from eventlet.green import urllib2

urls = [
    "http://www.google.com/intl/en_ALL/images/logo.gif",
    "http://python.org/images/python-logo.gif",
    "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif",
]

def fetch(url):
    # return urllib2.urlopen(url).read()
    return urllib.request.urlopen(url).read()

pool = eventlet.GreenPool(1000)#创建绿色线程池对象,可以指定数量
for body in pool.imap(fetch, urls): #协程根据指定要执行的函数依次执行获得url的信息
    print("got body", len(body))

2、socket服务器

import eventlet

def handle(fd):
    print("client connected")
    while True:   #典型的读取文件的操作
        # pass through every non-eof line
        x = fd.readline()
        if not x: break
        fd.write(x)
        fd.flush()         #将文件内容刷新到硬盘中
        print("echoed", x)
    print("client disconnected")

print("server socket listening on port 8000")   #服务器监听程序,响应客户端的请求
server = eventlet.listen(('127.0.0.1', 8000))  # (IP地址, 端口) 元组的形式表示
pool = eventlet.GreenPool(200)    #绿色线程池,允许并行访问

while True:
    new_sock, address = server.accept() #阻塞,等待客户端连接   返回socket对象和客户端的  IP地址和端口号
    print("accepted", address)
    pool.spawn_n(handle, new_sock.makefile('rw'))  #创建新的绿色线程然后去执行

3、使用GreenPile的例子

import eventlet
from eventlet.green import socket

def geturl(url):
    c = socket.socket()
    ip = socket.gethostbyname(url)
    c.connect((ip, 80))
    print('%s connected' % url)
    c.sendall('GET /\r\n\r\n')
    return c.recv(1024)

urls = ['www.google.com', 'www.yandex.ru', 'www.python.org']
pile = eventlet.GreenPile()
for x in urls:
    pile.spawn(geturl, x)

# 注意,如果函数引发任何异常,那么pile将充当函数返回值的集合,它们将在这里引发
for url, result in zip(urls, pile):
    print('%s: %s' % (url, repr(result)[:50]))

参考:https://blog.csdn.net/youyou1543724847/article/details/71404145
http://www.aboutyun.com/thread-20085-1-1.html
https://github.com/eventlet/eventlet/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周小董

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值