我有一个Django视图,需要从多个Web服务中检索搜索结果,将结果混合在一起并呈现它们。我之前从未在Django中做过任何多线程。什么是现代,高效,安全的方式?
我对此一无所知,但gevent似乎是一个合理的选择。我应该用吗?与Django一起玩得好吗?我应该去别处看看吗?
答案 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。这主要导致不稳定和更多的资源足迹。相反,我同意dokkaebi和Andrew Gorcester的评论。两种评论都有所不同,因为它实际上取决于你的任务是什么。
如果您可以将任务拆分为许多较小的任务,则可以创建处理这些子任务的多个视图。这些视图可以返回类似JSON的内容,并且可以通过前端的AJAX使用。像这样,您可以在“进入”时构建页面内容,用户无需等到整个页面加载完毕。
如果你的任务是一个巨大的块,你最好使用任务队列处理程序。 Celery考虑到了这一点。如果Celery太过分,你可以使用zeroMQ。这基本上像Andrew上面提到的那样工作:你安排处理任务并从你的前端页面轮询后端,直到任务完成(通常也通过AJAX)。您也可以在此处使用long polling之类的内容。
答案 2 :(得分:1)
我使用futures
,available in 3.2和backported 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…