纯Python协程异步I/O
项目描述
Bluelet是一个简单的、纯Python编写的异步套接字应用程序解决方案。它使用PEP 342协程来使并发I/O看起来和表现像顺序编程。
因此,它与Greenlet绿色线程库及其相关包Eventlet和Gevent类似。Bluelet具有更简单、100%纯Python实现,但与基于Greenlet的解决方案相比,牺牲了灵活性和性能。然而,它应该足够用于许多不需要严重可扩展性的应用程序;它可以被视为比asyncore更糟糕的替代品,或者是一个异步替代品SocketServer(以及更多)。
“回声”服务器
“回声”服务器是一个用于演示套接字编程的典型愚蠢示例。它简单接受连接,读取行,并将读取的每一行都写回客户端。
以下是一个使用普通Python套接字的示例
import socket listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listener.bind(('', 4915)) listener.listen(1) while True: sock, addr = listener.accept() while True: data = sock.recv(1024) if not data: break sock.sendall(data)
代码非常简单,但其同步性有一个主要问题:服务器一次只能接受一个连接。即使对于非常小的服务器应用程序也不行。
解决这个问题的方法是为每个运行相同同步代码的操作系统线程或进程进行fork。然而,这很快变得复杂,使得应用程序难以管理。Python的asyncore模块提供了一种编写接受同一操作系统线程中多个连接的异步服务器的方法
import asyncore import socket class Echoer(asyncore.dispatcher_with_send): def handle_read(self): data = self.recv(1024) self.send(data) class EchoServer(asyncore.dispatcher): def __init__(self): asyncore.dispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(('', 4915)) self.listen(1) def handle_accept(self): sock, addr = self.accept() handler = Echoer(sock) server = EchoServer() asyncore.loop()
异步I/O允许线程运行单个
Bluelet(类似于其他基于协程的异步I/O库)允许你编写看起来顺序但实际并发的代码。就像这样
import bluelet def echoer(conn): while True: data = yield conn.recv(1024) if not data: break yield conn.sendall(data) bluelet.run(bluelet.server('', 4915, echoer))
除了yield关键字外,请注意,此代码与我们的第一个顺序版本非常相似。(Bluelet还负责样板化的套接字设置代码。)这是因为echoer是一个Python协程:它每处提到yield都会暂时挂起其执行。然后Bluelet的调度器接管并等待事件,就像asyncore一样。当套接字事件发生时,协程会在yield的地方继续执行。因此,无需拆分代码;它可以作为一个单独的代码块出现。太棒了!
其他示例
此存储库还包括一些Bluelet编程模型的非平凡示例。
httpd
httpd.py示例使用不到100行Python代码实现了一个非常简单的Web服务器。启动程序,并在您的网页浏览器中导航到http://127.0.0.1:8088/,以查看其实际运行情况。
此示例演示了一个比上面描述的echo服务器稍微复杂一点的网络服务器的实现。同样,服务器的代码看起来就像一个顺序的、一次处理一个连接的程序,其中插入了yield表达式——但它运行并发,可以同时处理多个请求。
crawler
crawler.py演示了除了服务器之外,Bluelet还可以用于客户端代码。它实现了一个非常简单的异步HTTP客户端,并从Twitter API发起一系列推文的请求。
crawler.py程序实际上执行了四次相同的请求集,以比较它们的性能
顺序版本会发起一个请求,等待响应,然后发起下一个请求。
“线程”版本为每个请求启动一个操作系统线程,并发地发起所有请求。
“进程”版本使用Python的multiprocessing模块在单独的操作系统进程中发起每个请求。它使用multiprocessing模块方便的并行map实现。
Bluelet版本在每个Bluelet协程中运行每个HTTP请求。请求并发运行,但它们在一个进程中的一个线程中使用。
顺序实现几乎肯定是最慢的。其他三个实现都是并发的,应该具有大致相同的性能。基于线程和进程的实现会产生启动开销;multiprocessing实现可以通过避免GIL(但这可能不会太显著,因为网络延迟占主导地位)看到优势;Bluelet实现没有启动开销,但有一些可能减慢速度的调度逻辑。
crawler.py报告了每个实现的时间。在我的机器上,这是我所看到的
sequential: 4.62 seconds threading: 0.81 seconds multiprocessing: 0.13 seconds bluelet: 0.20 seconds
这些数字有些嘈杂,并且在不同运行之间有些不一致,但总的来说,我们看到Bluelet与其他两个并发实现具有竞争力,而顺序版本要慢得多。
基本用法
要开始使用Bluelet,你只需编写一个yield Bluelet事件的协程,并使用bluelet.run调用它
import bluelet def coro(): yield bluelet.end() bluelet.run(coro())
bluelet.run接受一个生成器(一个正在运行的协程)作为参数,并运行它直到完成。这是进入Bluelet调度宇宙的入口。记住,在Python中,任何包含yield表达式的“函数”都是协程——这就是为什么coro如此特殊。
使用Bluelet编程的关键在于在通常需要阻塞或与Bluelet调度器交互的地方使用yield表达式。从技术角度看,每个yield语句都会向正在运行的Bluelet调度器发送一个“事件”对象,但你通常可以不用考虑事件对象。以下是一些构成Bluelet网络套接字API的Bluelet yield表达式。
conn = yield bluelet.connect(host, port):连接到网络主机并返回一个可用于通信的“连接”对象。
yield conn.send(data):通过连接发送数据字符串。返回实际发送的数据量。
yield conn.sendall(data):发送数据字符串,持续发送数据块直到全部发送完毕。
data = yield conn.recv(bufsize):从连接接收数据。
data = yield conn.readline(delim="\n"):从连接读取一行数据,行由delim分隔。
server = bluelet.Listener(host, port):构建一个Bluelet服务器对象,可以用于异步等待连接。(这里没有yield;这是一个构造器。)
conn = yield server.accept():异步等待服务器的连接,返回上面提到的连接对象。
这些工具足以使用Bluelet构建异步客户端和服务器应用程序。还有一个方便的现成协程,称为bluelet.server,可以帮助你快速构建服务器应用程序。以下这行代码
bluelet.run(bluelet.server(host, port, handler_coro))
运行一个异步套接字服务器,监听并发连接。对于每个传入的连接conn,服务器调用handler_coro(conn)并将该协程添加到Bluelet调度器。
Bluelet还提供了一些封装了通用绿色线程能力的非套接字工具
res = yield bluelet.call(coro()):将另一个协程作为“子协程”调用,就像在普通Python代码中调用函数一样。严格来说,当前协程被挂起,而coro被启动;当coro完成后,Bluelet将控制权返回给当前协程并返回coro返回的值(参见下面的bluelet.end)。效果类似于Python提议的“yield from”语法。
res = yield coro():上述的简写。只需yield任何生成器对象就相当于使用bluelet.call。
yield bluelet.spawn(coro()):类似于call,但使子协程并发运行。两个协程都保留在线程调度器中。这是构建例如一次性处理多个网络连接的程序的方法(它由bluelet.server内部使用)。
yield bluelet.join(coro):挂起当前协程,直到之前使用spawn启动的给定线程完成。
yield bluelet.kill(coro):中止并取消已启动的线程的调度。
yield bluelet.end(value=None):终止当前协程,如果当前协程是另一个使用bluelet.call调用的,则返回指定的值给它。类似于普通Python中的return。
yield bluelet.sleep(duration):暂停当前协程大约 duration 秒,在间隔过后尽早恢复。
yield bluelet.null():不做任何特殊操作。这只是为了让其他协程有机会运行。当你需要在协程中执行长时间运行、阻塞的操作并希望其他绿色线程有机会完成工作时会很有用。
这些少量的 yield 语句组合在一起,就足以构建任何可以从简单的纯 Python 协作式多任务中受益的应用程序。
项目详情
bluelet-0.2.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | b1b85b6b819d9c31ece70af09226d404e7b838737cdc8a47cd8ab7b43f2d8e0c |
|
MD5 | 130228d6d07a6e0c5b4769eead7fb6fb |
|
BLAKE2b-256 | 0b4e1a1688bb660ed9012c94099b8d7cbc26cd57b636259fe959cecb42c52ab0 |