使用SQLAlchemy和多处理挂起Python脚本

2024-04-19 06:54:24 发布

您现在位置:Python中文网/ 问答频道 /正文

考虑下面的Python脚本,它使用SQLAlchemy和Python多处理模块。 这与Debian squeeze上的Python2.6.6-8+b1(默认)和SQLAlchemy 0.6.3-3(默认)一起使用。 这是一些实际代码的简化版本。

import multiprocessing
from sqlalchemy import *
from sqlalchemy.orm import *
dbuser = ...
password = ...
dbname = ...
dbstring = "postgresql://%s:%s@localhost:5432/%s"%(dbuser, password, dbname)
db = create_engine(dbstring)
m = MetaData(db)

def make_foo(i):
    t1 = Table('foo%s'%i, m, Column('a', Integer, primary_key=True))

conn = db.connect()
for i in range(10):
    conn.execute("DROP TABLE IF EXISTS foo%s"%i)
conn.close()
db.dispose()

for i in range(10):
    make_foo(i)

m.create_all()

def do(kwargs):
    i, dbstring = kwargs['i'], kwargs['dbstring']

    db = create_engine(dbstring)
    Session = scoped_session(sessionmaker())
    Session.configure(bind=db)
    Session.execute("COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;")
    Session.commit()
    db.dispose()

pool = multiprocessing.Pool(processes=5)               # start 4 worker processes
results = []
arglist = []
for i in range(10):
    arglist.append({'i':i, 'dbstring':dbstring})
r = pool.map_async(do, arglist, callback=results.append) # evaluate "f(10)" asynchronously
r.get()
r.wait()
pool.close()
pool.join()

此脚本将挂起,并显示以下错误消息。

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.6/multiprocessing/pool.py", line 259, in _handle_results
    task = get()
TypeError: ('__init__() takes at least 4 arguments (2 given)', <class 'sqlalchemy.exc.ProgrammingError'>, ('(ProgrammingError) syntax error at or near "%"\nLINE 1: COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;\n        ^\n',))

当然,这里的语法错误是TRUNCATE foo%s;。我的问题是,为什么这个过程会挂起,我可以说服它以错误的方式退出,而不必对我的代码做大手术吗?这种行为与我的实际代码非常相似。

注意,如果语句被print foobarbaz之类的内容替换,则不会发生挂起。而且,如果我们更换

Session.execute("COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;")
Session.commit()
db.dispose()

只要Session.execute("TRUNCATE foo%s;")

我使用前一个版本是因为它更接近我的实际代码。

另外,从图片中删除multiprocessing并在表上连续循环会使挂起消失,它只是在出现错误时退出。

我还对错误的形式感到困惑,特别是TypeError: ('__init__() takes at least 4 arguments (2 given)'位。这个错误是从哪里来的?它似乎来自multiprocessing代码中的某个地方。

PostgreSQL日志没有帮助。我看到很多台词像

2012-01-09 14:16:34.174 IST [7810] 4f0aa96a.1e82/1 12/583 0 ERROR:  syntax error at or near "%" at character 28
2012-01-09 14:16:34.175 IST [7810] 4f0aa96a.1e82/2 12/583 0 STATEMENT:  COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;

但没有其他相关的。

更新1:多亏了lbolla和他的insightful analysis,我才能够提交一份关于这个的Python bug report。 请参阅该报告中的sbt分析,以及here。另请参见Python错误报告Fix exception pickling。因此,根据sbt的解释,我们可以用

import sqlalchemy.exc
e = sqlalchemy.exc.ProgrammingError("", {}, None)
type(e)(*e.args)

它给予

Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
TypeError: __init__() takes at least 4 arguments (2 given)

更新2:这已经被修复,至少对于SQLAlchemy,由Mike Bayer修复,请参见bug报告StatementError Exceptions un-pickable.。根据迈克的建议,我还向psycopg2报告了一个类似的bug,尽管我没有(也没有)一个实际的破损示例。不管怎样,他们显然已经修复了它,尽管他们没有给出修复的细节。见psycopg exceptions cannot be pickled。我还报告了一个Python错误ConfigParser exceptions are not pickleable对应于the SO question lbolla mentioned。看来他们想测试一下。

无论如何,在可预见的将来,这看起来将继续是一个问题,因为总的来说,Python开发人员似乎没有意识到这个问题,所以不要防范它。令人惊讶的是,似乎没有足够的人使用多处理来解决这一众所周知的问题,或者他们只是忍受了它。我希望Python开发人员至少可以在Python 3中修复它,因为它很烦人。

我接受了lbolla的回答,因为如果没有他对问题如何与异常处理相关的解释,我很可能在理解这一点上一事无成。我还要感谢sbt,他解释说,Python不能pickle异常是问题所在。我非常感谢他们两位,请投票给他们。谢谢。

更新3:我发布了一个后续问题:Catching unpickleable exceptions and re-raising


Tags: 代码inimportexecutedbsqlalchemysession错误