Многопроцессорность Python и доступ к базе данных с помощью pyodbc "небезопасны"?
Проблема:
Я получаю следующую трассировку и не понимаю, что это значит или как ее исправить:
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, полная данных для обработки. Я пытаюсь использовать модуль многопроцессорности для параллелизации работы и использования нескольких ядер на моем компьютере. Моя общая классная структура выглядит следующим образом:
- 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, поэтому я думаю, что это случается один раз для читателя и один раз для писателя. Мои рабочие процессы создаются отлично, но просто сидите там, пока я не отправлю KeyboardInterrupt, потому что они не имеют ничего в work_queue
.
У читателя и писателя есть собственное подключение к базе данных, созданное при инициализации.
Решение:
Спасибо Mark и Ferdinand Beyer за их ответы и вопросы, которые привели к этому решению. Они по праву отметили, что объект Cursor не "pickle-able", который является методом, который использует многопроцессорность для передачи информации между процессами.
Проблема с моим кодом заключалась в том, что MyReaderClass(multiprocessing.Process)
и MyWriterClass(multiprocessing.Process)
оба связаны с базой данных в своих методах __init__()
. Я создал оба этих объекта (т.е. Их метод init) в MyManagerClass
, а затем вызвал start()
.
Таким образом, он создаст объекты соединения и курсора, а затем попытается отправить их дочернему процессу через pickle. Мое решение состояло в том, чтобы перенести экземпляр объекта соединения и курсора на метод run(), который не вызывается до тех пор, пока дочерний процесс не будет полностью создан.
Ответы
Ответ 1
Многопроцессорность полагается на травление для обмена объектами между процессами. Соединение podbc и объекты курсора нельзя травить.
>>> 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
"Он помещает элементы в work_queue", какие предметы? Возможно ли, что объект курсора тоже пройдет?
Ответ 2
Ошибка возникает в модуле pickle
, поэтому где-то ваш объект DB-Cursor становится маринованным и незакрашенным (сериализован для хранения и неэтериализован для объекта Python снова).
Я предполагаю, что pyodbc.Cursor
не поддерживает травление. Почему вы все равно должны пытаться сохранить объект курсора?
Проверьте, используете ли вы pickle
где-то в своей рабочей цепочке или если он используется неявно.
Ответ 3
pyodbc имеет Python DB-API уровень безопасности потоков 1. Это означает, что потоки не могут обмениваться соединениями, и они не являются потокобезопасными вообще.
Я не думаю, что базовые потокобезопасные драйверы ODBC имеют значение. Это в коде Python, как указано ошибкой Pickling.