Python多进程和使用pyodbc访问数据库“不安全”?

15

问题:

我得到了以下的回溯信息,但不明白其含义或如何解决:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Python26\lib\multiprocessing\forking.py", line 342, in main
    self = load(from_parent)
  File "C:\Python26\lib\pickle.py", line 1370, in load
    return Unpickler(file).load()
  File "C:\Python26\lib\pickle.py", line 858, in load
    dispatch[key](self)
  File "C:\Python26\lib\pickle.py", line 1083, in load_newobj
    obj = cls.__new__(cls, *args)
TypeError: object.__new__(pyodbc.Cursor) is not safe, use pyodbc.Cursor.__new__()

情况:

我有一个装满需要处理的数据的 SQL Server 数据库。我试图使用 multiprocessing 模块来并行化处理,利用计算机上的多个核心。我的一般类结构如下:

  • MyManagerClass
    • 这是主要的类,程序从这里开始。
    • 它创建两个 multiprocessing.Queue 对象,一个是 work_queue,另一个是 write_queue
    • 它还创建和启动其他进程,然后等待它们完成。
    • 注意:这不是 multiprocessing.managers.BaseManager() 的扩展
  • MyReaderClass
    • 这个类从 SQL Server 数据库中读取数据。
    • 它将项目放入 work_queue 中。
  • MyWorkerClass
    • 这是工作处理发生的地方。
    • 它从 work_queue 获取项目,并将已完成的项目放入 write_queue 中。
  • MyWriterClass
    • 这个类负责将处理后的数据写回 SQL Server 数据库。
    • 它从write_queue获取项目。

想法是会有一个管理器、一个读者、一个写者和许多工人。

其他细节:

我在 stderr 中得到了两次 traceback,所以我认为它会在读者和写者那里发生一次。我的工人进程被成功创建,但是只会坐在那里,直到我发送 KeyboardInterrupt ,因为它们没有在 work_queue 中找到任务。

读者和写者都有自己的数据库连接,在初始化时创建。

解决方法:

感谢 Mark 和 Ferdinand Beyer 的答案和问题,他们指出 Cursor 对象不可“pickle-able”,这是 multiprocessing 用来在进程之间传递信息的方法。

我的代码问题在于 MyReaderClass(multiprocessing.Process)MyWriterClass(multiprocessing.Process) 在它们的 __init__() 方法中都连接到数据库。我在 MyManagerClass 中创建了这两个对象(即调用了它们的 init 方法),然后调用了 start()

因此,它会创建连接和游标对象,然后尝试通过 pickle 将它们发送到子进程。我的解决方案是将连接和游标对象的实例化移动到 run() 方法中,该方法在子进程完全创建之前不会被调用。


只是想说:非常好的问题。 - mavnn
3个回答

14
多进程依赖于 pickling 来在进程间传递对象。pyodbc 连接和游标对象无法被 pickled。
>>> cPickle.dumps(aCursor)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.5/copy_reg.py", line 69, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle Cursor objects
>>> cPickle.dumps(dbHandle)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.5/copy_reg.py", line 69, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle Connection objects

“它将项目放入工作队列”,这些项目是什么?光标对象是否也会被传递? ```

"它将项目放入工作队列",这里的项目指的是什么?光标对象是否也可能被传递进去了?

```

我有一个生成器,它循环遍历游标中的项目(基本上调用pyodbyc.Cursor().fetchone())。我相信它会产生一个元组(id, stuff_to_process),这就是我放入队列中的内容。我尝试进行深拷贝,但那并没有起作用。我查看了帮助文档,发现它实际上是一个Row对象的实例。因此,我可能需要先转换为元组。 - tgray
Row对象必须包含对Cursor或其他内容的引用。 - tgray
也许是因为我在MyManagerClass中创建了阅读器和写入器,连接/游标在__init__()中创建,所以技术上它们是在管理进程中创建的,然后被管道传输到自己的子进程? - tgray
我会尝试将连接的实例化移动到Run方法中。 - tgray
有没有绕过无法 pickle 的对象的方法?今天我遇到了类似的问题。 - motoku

3

pyodbc是Python DB-API,遵循线程安全等级1。这意味着线程之间不能共享连接,并且完全不安全。

我认为底层线程安全的ODBC驱动程序并没有什么区别。正如Pickling错误所指出的那样,它在Python代码中。


3
错误发生在 pickle 模块中,因此您的 DB-Cursor 对象在某个地方被 pickled 和 unpickled(序列化到存储并再次反序列化为 Python 对象)。
我猜测 pyodbc.Cursor 不支持 pickling。无论如何,为什么要尝试持久化游标对象呢?
检查一下您是否在工作链中使用了 pickle 或者它是否被隐式使用。

看起来多进程隐式地使用它在进程之间通过管道对象传递东西(特别是我创建的队列对象)。 - tgray

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