【问题标题】:Can you perform multi-threaded tasks within Django?你能在 Django 中执行多线程任务吗?
【发布时间】:2013-07-11 19:24:08
【问题描述】:

我想要完成的序列:

  1. 用户单击网页上的按钮
  2. model.py 中的一些函数开始运行。例如,通过爬网收集一些数据
  3. 函数完成后,将结果返回给用户。

我应该在 model.py 中打开一个新线程来执行我的函数吗?如果是这样,我该怎么做?

【问题讨论】:

  • 你想完成什么?也许您可以通过 AJAx、WebSocket、魔术小马等前端技术来做到这一点......
  • 什么是魔法小马?在谷歌上找不到...

标签: python django multithreading


【解决方案1】:

this answer 所示,您可以使用线程包来执行异步任务。似乎每个人都推荐 Celery,但对于执行简单但长时间运行的任务来说,它通常是矫枉过正的。我认为使用线程实际上更容易、更透明。

这是一个简单的异步爬虫示例:

#views.py
import threading
from .models import Crawl

def startCrawl(request):
    task = Crawl()
    task.save()
    t = threading.Thread(target=doCrawl,args=[task.id])
    t.setDaemon(True)
    t.start()
    return JsonResponse({'id':task.id})

def checkCrawl(request,id):
    task = Crawl.objects.get(pk=id)
    return JsonResponse({'is_done':task.is_done, result:task.result})

def doCrawl(id):
    task = Crawl.objects.get(pk=id)
    # Do crawling, etc.

    task.result = result
    task.is_done = True
    task.save()

您的前端可以向startCrawl 发出请求以开始抓取,它可以发出Ajax 请求以使用checkCrawl 对其进行检查,这将在完成时返回true 和结果。


Python3 更新:

threading 库的The documentation 建议将daemon 属性作为关键字参数传递,而不是使用setter:

t = threading.Thread(target=doCrawl,args=[task.id],daemon=True)
t.start()

Python

As discussed herethis bug 会导致缓慢的内存泄漏,从而导致长时间运行的服务器溢出。该错误已针对 Python 3.7 及更高版本进行了修复。

【讨论】:

  • 在线程完成之前,为服务 Web 请求而创建的进程不会运行吗?
  • @SandeepBalagopal 这是一个很好的观点,您可能是对的,但是您仍然会在该进程和守护进程退出之前向用户返回响应。由于最大进程数是操作系统级别的问题,我想您的架构将决定此解决方案的可行性限制。从这个意义上说,消息队列更健壮,或者您可以使用队列库docs.python.org/3.7/library/queue.html
  • @nbwoodward 是否意味着在守护线程完成之前,工作人员将无法在这段时间内处理新请求?在这种情况下会不会导致低吞吐量 (RPS)?
  • 我正在为我的网页使用这种确切的方法,并且(还)没有任何问题。
  • @Flimm 这是个好问题。线程安全涉及从单独的线程访问内存中的值。您的问题与并发数据库访问更相关。 Django 天生就是为处理所有可能访问数据库的多个线程和/或进程上的并发请求而构建的。所以在我看来,ORM 也应该能够处理线程库的并发性。
【解决方案2】:
  1. 是的,它可以多线程,但通常使用 Celery 来做等价。 You can read about how in the celery-django tutorial.
  2. 实际上很少想要强迫用户等待网站。虽然这总比冒险超时要好。

这是你所描述的一个例子。

User sends request
Django receives => spawns a thread to do something else.
main thread finishes && other thread finishes 
... (later upon completion of both tasks)
response is sent to user as a package.

更好的方法:

User sends request
Django receives => lets Celery know "hey! do this!"
main thread finishes
response is sent to user
...(later)
user receives balance of transaction 

【讨论】:

  • Celery 在很多方面都太过分了。请停止推荐它作为任何不需要阻止请求/响应的灵丹妙药。这就像每当有人问如何存储一行文本时推荐一个 RDBMS。
  • @andybak 随意提出替代方案。对我来说,这听起来像是一个合法的用途。
  • 取决于具体情况,但您可以只生成一个线程并轮询完成,您可以使用一个简单的 cron 作业来检查任务,或者如果您确实需要更多功能,您可以使用以下几个之一“不像 celery 那样复杂”的项目,例如 huey 或 django-background-tasks。
  • Celery 在很多情况下过于重量级,不应该作为涉及异步工作的请求的后备位置。如果异步事务要占用一分钟的 CPU 时间,那好吧,去 Celery。当用户登录时,我想将某些用户数据提取到 memcache 中,以便在他们浏览我的系统时快速访问它。为此,芹菜很烂。不过,我不希望用户登录页面在缓存发生时被阻止。 Django 在某些方面非常出色,但如果您依赖于顺序的外部 RPC(ORM、memcache 等),它会不顾一切地把循环/内存冲到马桶上。
  • (如果您有其他建议,请提出答案。我推荐了一些我过去看到工作的东西,它可能已经过时,也可能是一个战斧,但它碰巧奏效了。我们拥有这个网站的主要原因之一是人们可以提出替代答案,而不仅仅是在 cmets 上一次性提出)。
【解决方案3】:

如果你不想在你的项目中添加一些矫枉过正的框架,你可以简单地使用subprocess.Popen:

def my_command(request):
    command = '/my/command/to/run'  # Can even be 'python manage.py somecommand'
    subprocess.Popen(command, shell=True)
    command = '/other/command/to/run'
    subprocess.Popen(command, shell=True)
    return HttpResponse(status=204)

[edit] 如 cmets 中所述,这不会启动后台任务并立即返回 HttpResponse。它将并行执行这两个命令,然后在两者都完成后返回 HttpResponse。这是 OP 要求的。

【讨论】:

  • 这不起作用(至少在我相当标准的 django + uwsgi + nginx 设置中)在启动长时间运行的任务以在后台搅动时快速返回 HTTP 响应。相反,它会启动子进程,但在子进程终止之前不会返回 HTTP 响应(即使您在命令末尾添加了“&”)。此外,如果网络服务器超时,它会终止无法完成的进程。例如,尝试使用 /bin/sleep 15(需要 15 秒)或 /bin/sleep 60/bin/sleep 900 && echo 'hello' > /tmp/tmptest123(将超时且未完成)的命令。
  • 确实如此,但这不是 OP 所要求的。 subprocess 将让您运行多线程函数并在完成后返回 http 响应。
猜你喜欢
  • 1970-01-01
  • 2016-02-21
  • 2018-10-07
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
  • 2011-08-05
  • 1970-01-01
  • 2017-06-16
相关资源
最近更新 更多