2009-10-08 17 views
10

El problema:Python multiprocesamiento y acceso a la base de datos con pyodbc "no es seguro"?

estoy recibiendo el siguiente rastreo y no entiendo lo que significa o cómo solucionarlo:

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__() 

La situación:

tengo obtuve una base de datos de SQL Server llena de datos para ser procesada. Intento utilizar el módulo de multiprocesamiento para paralelizar el trabajo y aprovechar los múltiples núcleos de mi computadora. Mi estructura de clases general es el siguiente:

  • MyManagerClass
    • Esta es la clase principal, donde se inicia el programa.
    • crea dos objetos, uno multiprocessing.Queue work_queue y uno write_queue
    • También crea y pone en marcha los otros procesos, a continuación, espera a que termine.
    • NOTA: esto es no una extensión de multiprocessing.managers.BaseManager()
  • MyReaderClass
    • Esta clase lee los datos de la base de datos de SQL Server.
    • Pone elementos en el work_queue.
  • MyWorkerClass
    • Aquí es donde ocurre el proceso de trabajo.
    • Obtiene elementos del work_queue y coloca los elementos completados en el write_queue.
  • MyWriterClass
    • Esta clase es el encargado de escribir los datos procesados ​​de nuevo a la base de datos de SQL Server.
    • Obtiene elementos del write_queue.

La idea es que habrá un director, un lector, un escritor, y muchos trabajadores.

Otros detalles:

me sale el rastreo dos veces en stderr, así que estoy pensando que ocurre una vez para el lector y una vez por el escritor. Mis procesos de trabajo se crean bien, pero simplemente se sientan allí hasta que envíe un KeyboardInterrupt porque no tienen nada en el work_queue.

Tanto el lector como el escritor tienen su propia conexión a la base de datos, creada en la inicialización.

Solución:

Gracias a Mark y Ferdinand Beyer para sus respuestas y preguntas que llevaron a esta solución. Con razón señalaron que el objeto Cursor no es "seleccionable", que es el método que usa el multiprocesamiento para pasar información entre procesos.

El problema con mi código era que MyReaderClass(multiprocessing.Process) y MyWriterClass(multiprocessing.Process) ambos conectados a la base de datos en sus __init__() métodos. Creé ambos objetos (es decir, llamé su método init) en MyManagerClass, y luego llamé al start().

por lo que sería crear los objetos de conexión y el cursor, y luego tratar de enviarlos al proceso hijo a través de la salmuera. Mi solución fue mover la creación de instancias de los objetos de conexión y cursor al método run(), que no se llama hasta que el proceso hijo esté completamente creado.

+0

Solo para decir: excelente pregunta. – mavnn

Respuesta

8

El multiprocesamiento se basa en el decapado para comunicar objetos entre procesos. La conexión pyodbc y los objetos del cursor no se pueden escalar.

>>> 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 

"Pone los elementos de la work_queue", qué elementos? ¿Es posible que también se pase el objeto cursor?

+0

Tengo un generador que hace un bucle sobre elementos en un cursor (básicamente llama a pyodbyc.Cursor(). Fetchone()). Creo que produce una tupla de (id, stuff_to_process) que es lo que pongo en la cola. Traté de hacer una copia profunda, pero eso no funcionó. Miré la ayuda y en realidad es una instancia de un objeto Row. Así que puede que necesite convertir a una tupla primero. – tgray

+0

El objeto Row debe contener una referencia al Cursor o algo. – tgray

+0

Convertir la fila a una tupla no lo resolvió. – tgray

3

El error se produce en el módulo pickle, por lo que en algún lugar su objeto DB-Cursor se saltará y se deshará (se serializará en el almacenamiento y se volverá a serializar en el objeto Python nuevamente).

supongo que no es compatible con pyodbc.Cursor decapado. ¿Por qué debería intentar persistir el objeto del cursor de todos modos?

Comprobar si utiliza pickle en alguna parte de su cadena de trabajo o si se utiliza implícitamente.

+0

Parece que el multiprocesamiento lo usa implícitamente para pasar cosas a través de objetos Pipe entre procesos (específicamente los objetos Queue creados). – tgray

1

pyodbc tiene Python DB-API threadsafety level 1. Esto significa que los hilos no pueden compartir conexiones, y no es seguro para nada.

No creo que los controladores ODBC subyacentes seguros para subprocesos marquen la diferencia. Está en el código de Python como lo señala el error de Pickling.

Cuestiones relacionadas