SQLite3 und Multithreading

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Einen wunderschönen guten Morgen allerseits.

Ich habe gerade eine mittelschwere Kriese mit dem sqlite3-Modul (Python 2.5) und Multithreading. Wie man ja an allen Ecken und Enden lesen kann, vertragen sich diese beiden Dinge nicht besonders gut. Da ich momentan aber nur einen Prototypen entwickle, wollte ich nicht grossartig noch eine Datenbank aufsetzen. Momentan bekomme ich folgende Fehlermeldung:

Code: Alles auswählen

Exception in thread Thread-1:
Traceback (most recent call last):
  File "c:\programme\python25\lib\threading.py", line 460, in __bootstrap
    self.run()
  File "C:\Dokumente und Einstellungen\shempel\Projekte\StorageSystem\src\StorageServiceManager\Global\SQLiteWrapper.py", line 26, in run
    self.cur.execute(cmd, params)
ProgrammingError: SQLite objects created in a thread can only be used in that same thread.The object was created in thread id 3736 and this is thread id 556
Die Fehlermeldung in der letzten Zeile ist wie ich finde, doch recht aussagekräftig, daher habe ich einfach mal ein kleines Workaround geschrieben:

Code: Alles auswählen

import sqlite3
import threading
import Queue

class SQLiteWrapper(threading.Thread):
    def __init__(self, db_file):
        threading.Thread.__init__(self)
        
        #DB laden
        self.con = sqlite3.connect(db_file)
        self.cur = self.con.cursor()
        self.q = Queue.Queue()
    
    def command(self, cmd, params=tuple()):
        #Kommando in Warteschlange einreihen
        self.q.put((cmd, params))
    
    def close(self):
        self.q.put(("",""))
    
    def run(self):
        while True:
            cmd, params = self.q.get()
            
            if not cmd:
                break
            
            #Komando ausfhuehren: Hier Tritt Fehler auf
            self.cur.execute(cmd, params)

#Worker versucht Zeilen zu erzeugen
class Worker(threading.Thread):
    def __init__(self, w):
        threading.Thread.__init__(self)
        self.w = w
    
    def run(self):
        w.command("CREATE TABLE test (f1 text, f2 text)")
        
        for i in xrange(200):
            self.w.command("INSERT INTO test ('?', '?')", (str(i), str(i+1)))


if __name__ == "__main__":
    import tempfile
    fn = tempfile.mktemp()
    w = SQLiteWrapper(fn)
    w.start()

    x = Worker(w)
    x.start()
    x.join()
    
    w.close()
    w.cur.close()
    w.con.close()
Es gibt eine SQLiteWrapper, der gierig auf Aktionen wartet (ueber eine Wartschlange) und einen Worker, der bereitwillig Aktionen erzeugt. Der angesprochene Fehler tritt in "SQLiteWrapper.run" an der markierten Stelle auf.

Wie es aussieht, geht SQLite nicht nach den Threads von Python, sondern prueft die Windows-Threads, welches dem Zweck meines Wrappers natürlich wenig dienlich ist.

Meine Frage ist nun eigentlich, ob jemand eine Idee hat (oder noch besser: eine Lösung :D ), wie ich dieses Problemchen lösen kann, da ich um mehrere Worker und Multihreading unter keinen Umständen herum komme.

Vielen Dank schon mal für das Lesen des doch recht langen Threads, aber kürzer ging es nicht :-)

Sebastian
BlackJack

Die `__init__()` vom Wrapper wird ja noch im Haupt-Thread ausgeführt. Verschiebe das erzeugen des Connection-Objekts einfach in die `run()`-Methode. Dann wird das Objekt auch in dem Thread benutzt, in dem es erzeugt wurde.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Vielen Dank BlackJack, funktioniert wunderbar und ersprart mir wohl ein ganzes Stückchen arbeit :-)
Antworten