免责声明:我知道在SO上有几个类似的问题。我想我读的大多数,如果不是全部,但没有找到我真实问题的答案(见后文)。 我也知道使用celery或其他异步队列系统是实现长时间运行任务的最佳方式 - 或者至少使用cron管理的脚本。还有mod_wsgi doc about processes and threads,但我不确定我是否正确。
问题是:
使用此处列出的解决方案涉及哪些确切的风险/问题?它们中的任何一个都适用于长时间运行的任务(好吧,即使芹菜更适合)? 我的问题更多是关于理解wsgi和python / django的内部结构,而不是找到最好的整体解决方案。阻塞线程,对变量的不安全访问,僵尸处理等问题
让我们说:
mod_wsgi conf:
WSGIDaemonProcess NAME user=www-data group=www-data threads=25
WSGIScriptAlias / /path/to/wsgi.py
WSGIProcessGroup %{ENV:VHOST}
我认为这些是可用于启动单独的进程(广义上意味着)的选项,可以在快速返回对用户的响应的同时执行长时间运行的任务:
import os
if os.fork()==0:
long_process()
else:
return HttpResponse()
import subprocess
p = subprocess.Popen([sys.executable, '/path/to/script.py'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
(脚本可能是manage.py命令)
import threading
t = threading.Thread(target=long_process,
args=args,
kwargs=kwargs)
t.setDaemon(True)
t.start()
return HttpResponse()
NB。
由于Global Interpreter Lock,在CPython中只有一个线程可以同时执行Python代码(即使某些面向性能的库可能会克服此限制)。如果您希望应用程序更好地使用多核计算机的计算资源,建议您使用多处理。但是,如果要同时运行多个I / O绑定任务,则线程仍然是一个合适的模型。
主线程将快速返回(httpresponse)。产生的长线程是否会阻止wsgi为其他请求做其他事情?!
from multiprocessing import Process
p = Process(target=_bulk_action,args=(action,objs))
p.start()
return HttpResponse()
这应该解决线程并发问题,不应该吗?
所以这些是我能想到的选择。什么会起作用,什么不行,为什么?
答案 0 :(得分:26)
<强> os.fork 强>
fork将克隆父进程,在本例中,它是您的Django堆栈。由于你只是想运行一个单独的python脚本,这似乎是不必要的臃肿。
<强>子强>
预计使用subprocess
是互动的。换句话说,虽然你可以使用它来有效地生成一个进程,但是预计在某些时候你会在完成后终止它。如果你让一个人继续运行,Python可能会为你清理,但我猜这会导致内存泄漏。
<强>线程强>
线程是定义的逻辑单元。它们在调用run()
方法时启动,并在run()
方法执行结束时终止。这使得它们非常适合创建将在当前范围之外运行的逻辑分支。但是,如您所述,它们受Global Interpreter Lock的约束。
<强>多强>
这基本上是关于类固醇的线程。它具有线程的优点,但不受全局解释器锁的约束,并且可以利用多核架构。但是,结果使它们变得更加复杂。
因此,您的选择实际上归结为线程或多处理。如果你可以使用一个线程,它对你的应用程序有意义,那就去一个线程吧。否则,请使用多处理。
答案 1 :(得分:11)
我发现如果你需要在后台运行一些长任务,使用uWSGI Decorators 相当比使用Celery更简单。 认为Celery是严重繁重项目的最佳解决方案,而且这是做简单事情的开销。
要开始使用uWSGI Decorators,您只需要使用
更新uWSGI配置<spooler-processes>1</spooler-processes>
<spooler>/here/the/path/to/dir</spooler>
编写如下代码:
@spoolraw
def long_task(arguments):
try:
doing something with arguments['myarg'])
except Exception as e:
...something...
return uwsgi.SPOOL_OK
def myView(request)
long_task.spool({'myarg': str(someVar)})
return render_to_response('done.html')
在uWSGI日志中启动视图时出现:
[spooler] written 208 bytes to file /here/the/path/to/dir/uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414
任务结束时:
[spooler /here/the/path/to/dir pid: 31138] done with task uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414 after 78 seconds
有一些奇怪的(对我来说)限制:
- spool can receive as argument only dictionary of strings, look like because it's serialize in file as strings.
- spool should be created on start up so "spooled" code it should be contained in separate file which should be defined in uWSGI config as <import>pyFileWithSpooledCode</import>
答案 2 :(得分:3)
问题:
产生的长线程是否会阻止wsgi执行其他操作 另一个请求?!
答案是否定的。
您仍然需要小心从请求中创建后台线程,以防您只是创建大量的后台线程并阻塞整个过程。即使你正在处理过程中,你真的需要一个任务排队系统。
关于从Web进程执行fork或exec,特别是来自Apache,这通常不是一个好主意,因为Apache可能会对创建的子进程的环境施加奇怪的条件,这可能在技术上干扰其操作。
使用像Celery这样的系统仍然是最好的解决方案。