eventlet语境下的“绿色线程”普通线程之间的区别:
1. 绿色线程几乎没有开销,不用像保留普通线程一样保留“绿色线程”,每一个网络连接对应至少一个“绿色线程”;
2. 绿色线程需要人为的设置使其互相让渡CPU控制权,而不是抢占。绿色线程既能够共享数据结构,又不需要显式的互斥控制,因为只有当一个绿色线程让出了控制权后其他的绿色线程才能访问彼此共享的数据结构。
下图是eventlet中协程、hub、线程、进程之间的关系:
_______________________________________ | python process | | _________________________________ | | | python thread | | | | _____ ___________________ | | | | | hub | | pool | | | | | |_____| | _____________ | | | | | | | greenthread | | | | | | | |_____________| | | | | | | _____________ | | | | | | | greenthread | | | | | | | |_____________| | | | | | | _____________ | | | | | | | greenthread | | | | | | | |_____________| | | | | | | | | | | | | ... | | | | | |___________________| | | | | | | | |_________________________________| | | | | _________________________________ | | | python thread | | | |_________________________________| | | _________________________________ | | | python thread | | | |_________________________________| | | | | ... | |_______________________________________|
绿色线程是线程内的概念,同一个线程内的绿色线程之间是顺序执行的,绿色线程之间想要实现同步,需要开发人员在阻塞的代码位置显式植入CPU让渡,此时hub接管进行调度,寻找同一个线程内另一个可调度的绿色线程。注意绿色线程是线程内的概念,不能跨线程同步。
eventlet基本API
一、孵化绿色线程
eventlet.spawn(func, *args, **kw)
该函数创建一个使用参数 *args 和 **kw 调用函数 func 的绿色线程,多次孵化绿色线程会并行地执行任务。该函数返回一个greenthread.GreenThread 对象,可以用来获取函数 func 的返回值。
eventlet.spawn_n(func, *args, **kw)
作用类似于spawn(),只不过无法获取函数 func 执行完成时的返回值或抛出的异常。该函数的执行速度更快
eventlet.spawn_after(seconds, func, *args, **kw)
作用同于spawn(),等价于 seconds 秒后执行spawn()。可以对该函数的返回值调用 GreenThread.cancel() 退出孵化和阻止调用函数 func
二、控制绿色线程
eventlet.sleep(seconds=0)
挂起当前的绿色线程,允许其他的绿色线程执行
class eventlet.GreenPool
控制并发的绿色线程池,可以控制并发度,进而控制整个并发所消耗的内存容量,或限制代码某一部分的连接数等
class eventlet.GreenPile
GreenPile 对象代表了工作块。该对象是一个可以向其中填充工作的迭代器,便于以后从其中读取结果
class eventlet.Queue
便于执行单元之间进行数据交流的基本构件,用于绿色线程之间的通信,
class eventlet.Timeout
可以向任何东西添加超时,在 timeout 秒后抛出异常 exception。当 exception 被忽视或为None时,Timeout 实例自身会被抛出。Timeout 实例是上下文管理器(context manager),因此可以在 with 语句中使用
三、补丁函数
eventlet.import_patched(modulename, *additional_modules, **kw_additional_modules)
引入标准库模块绿化后的版本,这样后续代码以非阻塞的形式执行,所需要的参数就是目标模块的名称,具体可参考 Import Green
eventlet.monkey_patch(all=True, os=False, select=False, socket=False, thread=False, time=False)
在全局中为指定的系统模块打补丁,补丁后的模块是“绿色线程友好的”,关键字参数指示哪些模块需要被打补丁,如果 all 是真,那么所有的模块会被打补丁而无视其他参数;否则才由具体模块对应的参数控制对指定模块的补丁。多数参数为与自己同名的模块打补丁,如os, time, select,但是 socket 参数为真时,如果 ssl 模块也存在,会同时补丁socket模块和ssl模块,类似的,thread参数为真时,会补丁thread, threading 和 Queue 模块。
可以多次调用monkey_patch(),详见 Monkeypatching the Standard Library
四、网络应用
eventlet.connect(addr, family=2, bind=None)
开启客户端套接字
参数:
- addr – 目标服务器的地址,对于 TCP 套接字,这该参数应该是一个 (host, port) 元组
- family – 套接字族,可选,详见 socket 文档
- bind – 绑定的本地地址,可选
返回:
连接后的“绿色” socket 对象
eventlet.listen(addr, family=2, backlog=50)
创建套接字,可以用于 serve() 或一个定制的 accept() 循环。设置套接字的 SO_REUSEADDR 可以减少打扰。
参数:
- addr:要监听的地址,比如对于 TCP 协议的套接字,这是一个(host, port) 元组。
- family:套接字族。
- backlog:排队连接的最大个数,至少是1,上限由系统决定。
返回:
监听中的“绿色”套接字对象。
eventlet.wrap_ssl(sock, *a, **kw)
将一个普通套接字转变为一个SSL套接字,与 ssl.wrap_socket() 的接口相同。可以使用 PyOpenSSL,但是在使用 PyOpenSSL 时会无视 cert_reqs 、ssl_version 、ca_certs 、do_handshake_on_connect 和suppress_ragged_eofs 等参数。
建议使用创建模式来调用该方法,如: wrap_ssl(connect(addr)) 或 wrap_ssl(listen(addr),server_side=True) 。这样不会出现“裸”套接字监听非SSL会话的意外。
返回:
“绿色” SSL 对象。
eventlet.serve(sock, handle, concurrency=1000)
在给定的套接字上运行服务器,对于每一个到来的客户端连接,会在一个独立的绿色线程中调用参数 handle ,函数 handle 接受两个参数,一是客户端的socket对象,二是客户端地址:
def myhandle(client_sock, client_addr): print("client connected", client_addr) eventlet.serve(eventlet.listen((‘127.0.0.1‘, 9999)), myhandle)
函数 handle 返回时将会关闭客户端套接字
serve() 会阻塞调用的绿色线程,直到服务器关闭才返回,如果需要绿色线程立即返回,可以为 serve() 孵化一个新的绿色线程
任何 handle 抛出的没有捕获的异常都会被当做serve()抛出的异常,造成服务器的终止,因此需要弄清楚应用会抛出哪些异常。handle 的返回值会被忽视。
抛出一个 StopServe 异常来妥善地结束server – that’s the only way to get the server() function to return rather than raise.
参数 concurrency 控制并发度,是任意时刻处理请求的绿色线程的数量上限,当服务器达到该上限时,它不会接受新的连接,直到有现有的完成为止。
class eventlet.StopServe
用于妥善退出 serve() 的异常类