2009-10-08 15 views
10

Il problema:L'elaborazione multiprocessing e database Python con pyodbc "non è sicuro"?

Sto ottenendo il seguente traceback e non capisco che cosa significa e come risolvere il problema:

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

ho ha ottenuto un database SQL Server pieno di dati da elaborare. Sto cercando di usare il modulo multiprocessing per parallelizzare il lavoro e sfruttare i molteplici core sul mio computer. La mia struttura generale di classe è la seguente:

  • MyManagerClass
    • Questa è la classe principale, dove il programma si avvia.
    • crea due oggetti, uno multiprocessing.Queue work_queue e uno write_queue
    • Inoltre, crea e lancia gli altri processi, quindi li attende per finire.
    • NOTA: questo è non un'estensione multiprocessing.managers.BaseManager()
  • MyReaderClass
    • Questa classe legge i dati dal database di SQL Server.
    • Inserisce elementi nello work_queue.
  • MyWorkerClass
    • Questo è dove il processo di lavorazione avviene.
    • Ottiene elementi dallo work_queue e inserisce gli elementi completati nello write_queue.
  • MyWriterClass
    • Questa classe si occupa di scrivere i dati elaborati al database di SQL Server.
    • Ottiene elementi dallo write_queue.

L'idea è che ci sarà un manager, un lettore, uno scrittore, e molti lavoratori.

Altri particolari:

ho la traceback due volte in stderr, così sto pensando che succede una volta per il lettore e una volta per lo scrittore. I miei processi di lavoro vengono creati bene, ma restano seduti fino a quando non invio un KeyboardInterrupt perché non hanno nulla nello work_queue.

Sia il lettore che lo scrittore dispongono di una propria connessione al database, creata durante l'inizializzazione.

Soluzione:

Grazie a Marco e Ferdinand Beyer per le loro risposte e le domande che hanno portato a questa soluzione. Giustamente hanno sottolineato che l'oggetto del Cursore non è "in grado di decollare", che è il metodo utilizzato dal multiprocessing per passare le informazioni tra i processi.

Il problema con il mio codice era che MyReaderClass(multiprocessing.Process) e MyWriterClass(multiprocessing.Process) erano entrambi collegati al database nei loro metodi __init__(). Ho creato entrambi questi oggetti (cioè chiamato il loro metodo init) in MyManagerClass, quindi chiamato start().

Quindi crea la connessione e gli oggetti del cursore, quindi prova a inviarli al processo figlio tramite pickle. La mia soluzione era spostare l'istanziazione della connessione e gli oggetti del cursore sul metodo run(), che non viene chiamato fino a quando il processo figlio non viene completamente creato.

+0

Solo per dire: ottima domanda. – mavnn

risposta

8

Il multiprocessing si basa sul decapaggio per comunicare oggetti tra processi. La connessione pyodbc e gli oggetti cursore non possono essere decapitati.

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

"Si mette elementi nella work_queue", quali elementi? È possibile che anche l'oggetto del cursore venga passato?

+0

Ho un generatore che scorre su oggetti in un cursore (in pratica chiama pyodbyc.Cursor(). Fetchone()). Credo che produca una tupla di (id, stuff_to_process) che è ciò che metto in coda. Ho provato a fare una copia profonda, ma non ha funzionato. Ho guardato l'aiuto ed è in realtà un'istanza di un oggetto Row. Quindi potrei aver bisogno di convertire prima una tupla. – tgray

+0

L'oggetto Row deve contenere un riferimento al Cursor o qualcosa del genere. – tgray

+0

La conversione della riga in una tupla non lo ha risolto. – tgray

3

L'errore viene generato nel modulo pickle, quindi da qualche parte l'oggetto DB-Cursor viene decapitato e non caricato (serializzato di nuovo nell'archivio e non serializzato nuovamente nell'oggetto Python).

Immagino che pyodbc.Cursor non supporti il ​​decapaggio. Perché dovresti provare a mantenere l'oggetto cursore comunque?

Controllare se si utilizza pickle da qualche parte nella catena di lavoro o se viene utilizzato implicitamente.

+0

Sembra che il multiprocessing usi implicitamente il passaggio delle cose attraverso gli oggetti Pipe tra processi (in particolare gli oggetti Queue che ho creato). – tgray

1

pyodbc ha Python DB-API threadsafety level 1. Questo significa che i thread non possono condividere connessioni, e non è affatto sicuro.

Non penso che i driver ODBC sottostanti thread-safe facciano la differenza. È nel codice Python come indicato dall'errore Pickling.