如何处理Python多进程数据库并发,特别是在Django中?

12

所以,我正在尝试编写一个应用程序,它使用django作为其ORM,因为它需要在后台执行一些处理操作并提供易于使用的前端界面。它的核心功能将是处理存储在数据库中的数据,使用高CPU进程(基本上是蒙特卡罗模拟),我想要实现多进程处理,具体使用Pool(我获取了4个进程)。基本上,我的代码像这样运行,父进程约有20个子进程:

assorted import statements to get the django environment in the script
from multiprocessing import Pool
from random import random
from time import sleep

def test(child):
    x=[]
    print child.id
    for i in range(100):
        print child.id, i
        x.append(child.parent.id) #just to hit the DB
    return x

if __name__ == '__main__':
    parent = Parent.objects.get(id=1)
    pool = Pool()
    results = []
    results = pool.map(test,parent.children.all())
    pool.close()
    pool.join()
    print results

使用这样的代码,我会间歇性地遇到DatabaseErrorPicklingError。前者通常是"数据库格式错误"或"与MySQL服务器的连接丢失"等形式,后者通常是"无法对模型进行pickle.DoesNotExist"。它们是随机发生的,会出现在任何进程中,并且当然数据库本身没有问题。如果我设置pool = Pool(proccesses=1),那么程序就可以运行,在单个线程中完美运行。我还加入了各种打印语句来确保大部分情况下实际上正在运行。

我还一直在将test更改为:

def test(child):
    x=[]
    s= random()
    sleep(random())
    for i in range(100):
        x.append(child.parent.id)
    return x

这只会使每次迭代在运行前暂停不到一秒钟,这样一切都没问题。如果我将随机间隔减少到约500毫秒,它就开始出问题了。所以,很可能是并发问题,对吧?但只有4个进程在运行。我的问题是,如何在不提前大量转储数据的情况下解决这个问题?我已经使用了SQLite和MySQL进行了测试,但两者都无法解决这个问题。


既然这些进程是CPU绑定的,那么为什么不使用multiprocessing.Lock来避免所有对数据库的竞争条件呢? - Bakuriu
2个回答

9
好的,所以我确定(在朋友的帮助下)问题在于django正在为所有进程使用同一数据库连接。通常,在并发的数据库请求时,它们要么在同一个线程中(这种情况下GIL会起作用),要么在不同的线程上,这种情况下django会建立不同的数据库连接。但是在多进程情况下,Python会深复制所有内容,因此它会将同一个数据库连接传递给子进程,然后他们彼此干扰直到出现错误。

解决方案是从每个子进程内部触发新的db连接(这相对较快)。

from django import db
...
def sub_process():
    db.close_connection()
    #the rest of the sub_process' routines

#code that calls sub_process with the pool

不断地添加或删除那行代码,这肯定会解决所有问题。


1
谢谢!之前我一直在使用线程,但当我开始使用多进程时遇到了问题。你对这两种情况的解释让我受益匪浅。 - Lindlof
如果在多进程调用之前或之后存在数据库调用,则此操作将失败。 - Justas

3

实际上,我最近也遇到了相同的问题,并看到了这篇文章:Django多进程和数据库连接...

只需在子进程中调用连接关闭操作即可:

from django.db import connection 
connection.close()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接