虽然已经给出了直接的解决方案,但我会尝试回答你的第一个问题并解释为什么你的代码没有按预期工作。
声明:我目前维护Eventlet。为了适应合理的大小,本评论将包含许多简化。
协同式多线程的简要介绍
有两种方式实现
多线程,而Eventlet则利用协作式方法。其核心是
Greenlet库,它基本上允许您创建独立的“执行上下文”。我们可以将这样的上下文视为所有
本地变量的冻结状态和下一条指令的指针。基本上,
多线程=上下文+调度程序。Greenlet提供上下文,因此我们需要一个调度程序,即使得决定哪个上下文应该占用CPU的东西。为了做出决策,我们还需要运行一些代码。这意味着我们需要另一个上下文(绿色线程)。在Eventlet代码库中,这个特殊的绿色线程被称为
Hub。调度程序维护一个有序
set 上下文集合,这些上下文需要尽快运行-
运行队列和等待某些事情完成的上下文集合(例如,网络IO或时间限制睡眠)。
但由于我们正在进行合作式多任务处理,除非显式地将上下文切换到另一个上下文,否则一个上下文将无限期地执行。这是一种非常悲哀的编程风格,并且根据定义与现有库不兼容(指他们知道谁);因此,Eventlet提供了常见模块的
绿色版本,以这种方式更改,使它们转换为Hub而不是阻塞所有内容。然后,一些时间可能会花费在其他绿色线程或Hub的
等待外部事件实现中,在这种情况下,Hub会切换回起始该事件的绿色线程 - 并且它将继续执行。
完毕。现在回到你的问题。
eventlet.spawn
实际上是做什么的:它创建一个新的执行上下文,基本上就是在内存中分配一个对象。同时,它告诉调度程序将此上下文放入运行队列中,以便在第一个可能的时刻,Hub 将切换到新生成的函数。您的代码没有提供这样的时刻。没有地方明确地放弃执行权给其他绿色线程,对于 Eventlet,通常是通过 eventlet.sleep()
实现的。而且,由于您没有使用常见模块的绿色版本,因此在等待其他代码时隐式地放弃执行权的机会也不存在。最合适(如果不是唯一)的地方应该是您的 WSGI 服务器的接受循环:在等待下一个请求时,它应该给其他绿色线程运行的机会。在第一个答案中提到的 eventlet.monkey_patch()
只是用其相应的绿色版本替换所有(或子集)常见模块的一种便捷方式。
整体设计上不需要的意见
在单独的部分中,可以轻松跳过。如果您正在构建防错误软件,通常希望限制生成的线程(包括但不限于“green”)和进程的执行时间,并至少报告(记录)或响应其未处理的错误。在提供的代码中,生成的绿色线程可能在下一时刻或五分钟后(再次因为没有人放弃CPU)运行或失败并出现未处理的异常。幸运的是,Eventlet提供了两个解决方案:Timeout with_timeout() 允许限制等待时间(请记住,如果它不放弃,您无法限制它),GreenThread.link() 用于捕获所有异常。对于我来说,重新引发“主”代码中的异常很诱人,link()
可以轻松实现,但请考虑异常将从睡眠和IO调用中引发 - 这些是您放弃到Hub的地方。这可能会提供一些非常反直觉的回溯。
Traceback: File "x.py", line 36, in <module> app.run() File "/usr/lib/python2.7/site-packages/flask/app.py", line 772, in run run_simple(host, port, self, **options) File "/usr/lib/python2.7/site-packages/werkzeug/serving.py", line 622, in run_simple reloader_type) File "/usr/lib/python2.7/site-packages/werkzeug/_reloader.py", line 265, in run_with_reloader reloader.run() File "/usr/lib/python2.7/site-packages/werkzeug/_reloader.py", line 167, in run self._sleep(self.interval) TypeError: sleep()最多只能接受1个参数(给出了2个)
- MarSoft