如何从Django视图进行并发Web服务调用?

时间:2012-09-27 00:56:01

标签: python django concurrency

  

可能重复:
  Asynchronous HTTP calls in Python

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

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

3 个答案:

答案 0 :(得分: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服务调用,并加入所有这些线程。一旦所有这些加入呼叫都返回,结果就会出现,您就可以将它们混合在一起。

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

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

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

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

答案 1 :(得分:1)

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

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

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

  2. 如果你的任务是一个巨大的块,你最好使用任务队列处理程序。 Celery考虑​​到了这一点。如果Celery太过分,你可以使用zeroMQ。这基本上像Andrew上面提到的那样工作:你安排处理任务并从你的前端页面轮询后端,直到任务完成(通常也通过AJAX)。您也可以在此处使用long polling之类的内容。

答案 2 :(得分:1)

我使用futuresavailable in 3.2backported to earlier versions including 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…