0

可能重复:
Python 中的异步 HTTP 调用

我有一个 Django 视图,它需要从多个 Web 服务中检索搜索结果,将结果混合在一起并呈现它们。我以前从未在 Django 中做过任何多线程。什么是现代、高效、安全的方法?

我对此一无所知,但gevent似乎是一个合理的选择。我应该用那个吗?它与 Django 配合得好吗?我应该去别处看看吗?

4

3 回答 3

2

不确定gevent。最简单的方法是使用线程[*]。这是一个如何在 Python 中使用线程的简单示例:

# std lib modules. "Batteries included" FTW.
import threading
import time

thread_result = -1

def ThreadWork():
  global thread_result
  thread_result = 1 + 1
  time.sleep(5)  # phew, I'm tiered after all that addition!

my_thread = threading.Thread(target=ThreadWork)
my_thread.start()  # This will call ThreadWork in the background.
                   # In the mean time, you can do other stuff
y = 2 * 5  # Completely independent calculation.
my_thread.join()  # Wait for the thread to finish doing it's thing.
                  # This should take about 5 seconds,
                  # due to time.sleep being called
print "thread_result * y =", thread_result * y

您可以启动多个线程,让每个线程进行不同的 Web 服务调用,然后加入所有这些线程。一旦所有这些加入调用都返回,结果就出来了,你就可以混合它们了。

更高级的提示:你应该调用加入超时;否则,您的用户可能会无限期地等待您的应用向他们发送响应。更好的是让您在请求到达您的应用程序之前进行这些 Web 服务调用;否则,您的应用程序的响应能力取决于您所依赖的服务。

关于一般线程的警告:小心可以由两个(或更多)不同线程访问的数据。对相同数据的访问需要“同步”。最流行的同步设备是锁,但还有很多其他设备。threading.Lock 实现了一个锁。如果您不注意同步,您可能会在您的应用程序中写入“竞争条件”。众所周知,此类错误很难调试,因为它们无法可靠地重现。

在我的简单示例中,thread_result 在 my_thread 和主线程之间共享。我不需要任何锁,因为主线程直到 my_thread 终止才访问 thread_result。如果我没有调用 my_thread.join,结果有时会是 -10 而不是 20。你自己试试吧。

[*] Python 没有真正的线程,因为并发线程不会同时执行,即使您有空闲内核。但是,您仍然可以获得并发执行;当一个线程被阻塞时,其他线程可以执行。

于 2012-09-27T01:22:44.253 回答
2

我刚刚很好地解决了这个问题futures在 3.2 中可用并向后移植到包括 2.x 在内的早期版本

就我而言,我正在从内部服务中检索结果并对其进行整理:

def _getInfo(request,key):
    return urllib2.urlopen(
        'http://{0[SERVER_NAME]}:{0[SERVER_PORT]}'.format(request.META) +
        reverse('my.internal.view', args=(key,))
        , timeout=30)

…

    with futures.ThreadPoolExecutor(max_workers=os.sysconf('SC_NPROCESSORS_ONLN')) as executor:
        futureCalls = dict([ (
            key,executor.submit(getInfo,request,key)
        ) for key in myListOfItems ])
        curInfo = futureCalls[key]
        if curInfo.exception() is not None:
            # "exception calling for info: {0}".format(curInfo.exception())"
        else:
            # Handle the result…
于 2012-11-08T15:57:55.800 回答
1

gevent 不会帮助您更快地处理任务。在资源占用方面,它比线程更有效。当使用 Django(通常通过 gunicorn)运行 gevent 时,您的 Web 应用程序将能够处理比普通 django wsgi 应用程序更多的并发连接。

但是:我认为这与您的问题无关。您想要做的是在一个 Django 视图中处理一项庞大的任务,这通常不是一个好主意。我个人建议您不要在 Django 中为此使用线程或 gevents greenlets。我看到了独立 Python 脚本或守护程序或其他工具的意义,但不适用于 Web。这主要导致不稳定和更多的资源占用。相反,我同意dokkaebiAndrew Gorcester的评论。不过,这两种评论都有所不同,因为这实际上取决于您的任务是什么。

  1. 如果您可以将您的任务拆分为许多较小的任务,您可以创建多个视图来处理这些子任务。这些视图可以返回类似于 JSON 的内容,并且可以通过前端的 AJAX 使用。像这样,您可以在页面“进入”时构建页面内容,而用户无需等到整个页面加载完毕。

  2. 如果您的任务是一大块,那么最好使用任务队列处理程序。芹菜浮现在脑海。如果 Celery 太过分了,你可以使用zeroMQ。这基本上像上面提到的 Andrew 一样工作:您安排任务进行处理并从前端页面轮询后端,直到任务完成(通常也通过 AJAX)。你也可以在这里使用长轮询之类的东西。

于 2012-09-27T01:52:17.883 回答