3

我正在为数据分析任务编写一个 Web UI。

这是它应该工作的方式:

在用户指定和 等参数后datasetlearning rate我创建一个新的task record,然后异步启动此任务的执行程序(执行程序可能需要很长时间才能运行。),并将用户重定向到其他页面。

搜索后async library for python,我从 开始eventlet,这是我在flask视图函数中写的:

db.save(task)
eventlet.spawn(executor, task)
return redirect("/show_tasks")

使用上面的代码,执行程序根本没有执行。

我的代码可能有什么问题?或者也许我应该尝试别的?

4

3 回答 3

7

当您获得直接解决方案时,我将尝试回答您的第一个问题并解释为什么您的代码无法按预期工作。

披露:我目前维护 Eventlet。此评论将包含一些简化以适应合理的大小。

协作多线程简介

有两种方法可以做多线程和 Eventlet 利用协作方法。核心是Greenlet库,它基本上允许您创建独立的“执行上下文”。可以将这样的上下文视为所有局部变量的冻结状态和指向下一条指令的指针。基本上,多线程 = 上下文 + 调度程序。Greenlet 提供了上下文,所以我们需要一个调度器,它可以决定哪个上下文现在应该占用 CPU。反过来,为了做出决定,我们还应该运行一些代码。这意味着一个单独的上下文(绿色线程)。这个特殊的绿色线程在 Eventlet 代码库中称为Hub 。调度器维护一个有序需要尽快运行的上下文 -运行队列和一组正在等待某事(例如网络 IO 或限时睡眠)完成的上下文。

但是由于我们在进行协作式多任务处理,一个上下文将无限期地执行,除非它明确地让步给另一个上下文。这将是一种非常可悲的编程风格,并且根据定义与现有库不兼容(指向他们知道谁);所以 Eventlet 所做的是它提供了通用模块的绿色版本,改变了它们切换到 Hub 而不是阻塞一切的方式。然后,可能会在其他绿色线程或 Hub 的等待外部事件实现中花费一些时间,在这种情况下,Hub 将切换回发起该事件的绿色线程 - 并且它将继续执行。

结尾。现在回到你的问题。


eventlet.spawn实际作用:它创建了一个新的执行上下文。基本上,在内存中分配一个对象。它还告诉调度程序将此上下文放入运行队列,因此在第一时间,Hub 将切换到新生成的函数。您的代码没有提供这样的时刻。没有地方可以明确放弃对其他绿色线程的执行,对于 Eventlet,这通常是通过eventlet.sleep(). 而且由于您不使用绿色版本的通用模块,因此在其他代码等待时没有机会隐式让步。最合适的(如果不是唯一的)地方是你的 WSGI 服务器的接受循环:它应该让其他绿色线程有机会在等待下一个请求时运行。在第一个答案中提到eventlet.monkey_patch()只是用相应的绿色版本替换所有(或部分)通用模块的便捷方式。


对整体设计的不受欢迎的意见 在单独的部分中,可以轻松跳过。如果您正在构建防错软件,您通常希望限制衍生线程(包括但不限于“绿色”)和进程的执行时间,并至少报告(日志)或对其未处理的错误作出反应。在提供的代码中,从技术上讲,您生成的绿色线程可能会在下一刻或五分钟后运行(同样,因为没有人产生 CPU)或因未处理的异常而失败。幸运的是,Eventlet 为这两个问题提供了两种解决方案:Timeout with_timeout()允许限制等待时间(请记住,如果它不产生,你不可能限制它)和GreenThread.link()捕获所有异常。在“主”代码中重新引发异常可能很诱人(对我来说是这样),并且link()很容易做到这一点,但考虑到异常会从睡眠和 IO 调用中引发——在你屈服于 Hub 的地方。这可能会提供一些非常反直觉的回溯。

于 2013-01-09T02:12:18.143 回答
4

您需要修补一些系统库才能使 eventlet 工作。这是一个最小的工作示例(也作为gist):

#!/usr/bin/env python 

from flask import Flask 
import time 
import eventlet 

eventlet.monkey_patch() 

app = Flask(__name__) 
app.debug = True 

def background(): 
    """ do something in the background """ 
    print('[background] working in the background...') 
    time.sleep(2) 
    print('[background] done.') 
    return 42 

def callback(gt, *args, **kwargs): 
    """ this function is called when results are available """ 
    result = gt.wait() 
    print("[cb] %s" % result) 

@app.route('/') 
def index(): 
    greenth = eventlet.spawn(background) 
    greenth.link(callback) 
    return "Hello World" 

if __name__ == '__main__': 
    app.run() 

更多关于:

编写像 Eventlet 这样的库的挑战之一是内置的网络库本身并不支持我们需要的那种合作产出。

于 2013-01-06T07:24:19.910 回答
2

Eventlet 可能确实适合您的目的,但它不仅适用于任何旧应用程序;Eventlet 要求它控制您应用程序的所有 I/O。

你也许可以逃脱

  1. 在另一个线程中启动 Eventlet 的主循环,甚至

  2. 不使用 Eventlet 而只是在另一个线程中生成您的任务。

芹菜可能是另一种选择。

于 2013-01-06T07:18:00.613 回答