Многопроцессорность 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
    • This is the main class, where the program starts.
    • Он создает два объекта multiprocessing.Queue, один work_queue и один write_queue.
    • Он также создает и запускает другие процессы, а затем ждет их завершения.
    • ПРИМЕЧАНИЕ: это не расширение multiprocessing.managers.BaseManager()
  • MyReaderClass
    • This class reads the data from the SQL Server database.
    • Он помещает элементы в папку work_queue.
  • MyWorkerClass
    • This is where the work processing happens.
    • Он получает элементы из work_queue и помещает завершенные элементы в write_queue.
  • MyWriterClass
    • This class is in charge of writing the processed data back to the SQL Server database.
    • Он получает предметы из write_queue.

Идея состоит в том, что будет один менеджер, один читатель, один писатель и много работников.

Другие сведения:

Я дважды получаю трассировку в stderr, поэтому я думаю, что это происходит один раз для читателя и один раз для писателя. Мои рабочие процессы создаются нормально, но просто сидим там, пока я не отправлю KeyboardInterrupt, потому что у них ничего нет в файле work_queue.

И читатель, и писатель имеют собственное соединение с базой данных, созданное при инициализации.

Решение:

Спасибо Марку и Фердинанду Бейеру за их ответы и вопросы, которые привели к этому решению. Они справедливо указали на то, что объект Cursor не может быть обработан, что является методом многопроцессорной обработки для передачи информации между процессами.

Проблема с моим кодом заключалась в том, что MyReaderClass(multiprocessing.Process) и MyWriterClass(multiprocessing.Process) оба подключались к базе данных в своих методах __init__(). Я создал оба этих объекта (т.е. назвал их метод инициализации) в MyManagerClass, а затем вызвал start().

Таким образом, он создаст объекты соединения и курсора, а затем попытается отправить их дочернему процессу через рассол. Мое решение состояло в том, чтобы переместить создание экземпляров объектов соединения и курсора в метод run(), который не вызывается до тех пор, пока дочерний процесс не будет полностью создан.


person tgray    schedule 08.10.2009    source источник
comment
Просто скажу: отличный вопрос.   -  person mavnn    schedule 09.12.2009


Ответы (3)


Многопроцессорность полагается на травление для передачи объектов между процессами. Соединение pyodbc и объекты курсора не могут быть промаринованы.

>>> 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», какие элементы? Возможно ли, что объект курсора также передается?

person Mark    schedule 08.10.2009
comment
У меня есть генератор, который перебирает элементы в курсоре (в основном вызывает pyodbyc.Cursor().fetchone()). Я считаю, что это дает кортеж (id, stuff_to_process), который я помещаю в очередь. Я пытался сделать глубокую копию, но это не сработало. Я просмотрел справку, и на самом деле это экземпляр объекта Row. Поэтому мне может понадобиться сначала преобразовать в кортеж. - person tgray; 08.10.2009
comment
Объект Row должен содержать ссылку на курсор или что-то в этом роде. - person tgray; 08.10.2009
comment
Может быть, это потому, что я создаю средства чтения и записи в MyManagerClass, а соединение/курсор создаются в __init__(), поэтому технически они создаются в процессе менеджера, а затем передаются в собственный дочерний процесс? - person tgray; 08.10.2009
comment
Я попробую перенести инстанцирование соединения в метод Run. - person tgray; 08.10.2009
comment
Есть ли обходной путь для травления unpickelable? Сегодня столкнулся с похожей проблемой. - person motoku; 03.06.2011

Ошибка возникает в модуле pickle, поэтому где-то ваш объект DB-Cursor подвергается консервации и распаковыванию (сериализуется в хранилище и снова несериализуется в объект Python).

Я предполагаю, что pyodbc.Cursor не поддерживает травление. Почему вы все равно должны пытаться сохранить объект курсора?

Проверьте, используете ли вы pickle где-то в своей рабочей цепочке или он используется неявно.

person Ferdinand Beyer    schedule 08.10.2009
comment
Похоже, что многопроцессорная обработка использует его неявно для передачи вещей через объекты Pipe между процессами (в частности, объекты Queue, которые я создал). - person tgray; 08.10.2009

pyodbc имеет Python DB-API уровень безопасности потоков 1. Это означает, что потоки не могут совместно использовать соединения, и это вообще не потокобезопасно.

Я не думаю, что базовые потокобезопасные драйверы ODBC имеют значение. Это в коде Python, как указано в ошибке Pickling.

person user3389572    schedule 30.10.2014