wake-up-neo.net

Python sqlite3 und Parallelität

Ich habe ein Python-Programm, das das "Threading" -Modul verwendet. Einmal pro Sekunde startet mein Programm einen neuen Thread, der einige Daten aus dem Web abruft und diese Daten auf meiner Festplatte speichert. Ich möchte sqlite3 verwenden, um diese Ergebnisse zu speichern, aber ich kann nicht, dass es funktioniert. Das Problem scheint die folgende Zeile zu sein:

conn = sqlite3.connect("mydatabase.db")
  • Wenn ich diese Codezeile in jeden Thread stecke, erhalte ich einen OperationalError, der mir mitteilt, dass die Datenbankdatei gesperrt ist. Ich vermute, dies bedeutet, dass ein anderer Thread mydatabase.db über eine sqlite3-Verbindung geöffnet hat und diese gesperrt hat.
  • Wenn ich diese Codezeile in das Hauptprogramm stelle und das Verbindungsobjekt (conn) an jeden Thread weitergebe, erhalte ich einen ProgrammingError, der besagt, dass in einem Thread erstellte SQLite-Objekte nur in demselben Thread verwendet werden können.

Zuvor hatte ich alle meine Ergebnisse in CSV-Dateien gespeichert und hatte keine dieser Dateisperren-Probleme. Hoffentlich wird dies mit sqlite möglich sein. Irgendwelche Ideen?

73
RexE

Sie können ein Consumer-Producer-Muster verwenden. Sie können beispielsweise eine Warteschlange erstellen, die von Threads gemeinsam genutzt wird. Der erste Thread, der Daten aus dem Web abruft, reiht diese Daten in die gemeinsam genutzte Warteschlange ein. Ein anderer Thread, dem die Datenbankverbindung gehört, nimmt die Warteschlange aus der Warteschlange und leitet sie an die Datenbank weiter. 

38
Lazin

Entgegen der landläufigen Meinung unterstützen neuere Versionen von sqlite3 do den Zugriff von mehreren Threads.

Dies kann über das optionale Schlüsselwortargument check_same_thread aktiviert werden:

sqlite.connect(":memory:", check_same_thread=False)
162
Jeremiah Rose

Folgendes finden Sie auf mail.python.org.pipermail.1239789

Ich habe die Lösung gefunden. Ich weiß nicht, warum die Python-Dokumentation kein einziges Wort zu dieser Option enthält. Wir müssen also der Verbindungsfunktion Ein neues Schlüsselwort-Argument hinzufügen, und wir können Cursor in einem anderen Thread erstellen. Verwenden Sie also:

sqlite.connect(":memory:", check_same_thread = False)

klappt perfekt für mich. Natürlich muss ich von jetzt an auf den sicheren Multithreading-Zugriff auf die Datenbank achten. Trotzdem alles Gute für den Versuch, zu helfen.

16
Robert Krolik

Sie sollten dafür gar keine Threads verwenden. Dies ist eine triviale Aufgabe für twisted und würde Sie wahrscheinlich trotzdem wesentlich weiter bringen.

Verwenden Sie nur einen Thread, und veranlassen Sie den Abschluss der Anforderung, ein Ereignis zum Schreiben auszulösen.

twisted kümmert sich um die Planung, Rückrufe, etc ... für Sie. Es wird Ihnen das gesamte Ergebnis als String übergeben, oder Sie können es über einen Stream-Prozessor ausführen (ich habe eine Twitter-API und eine Friendfeed-API ), die beide Ereignisse als Ergebnis abrufen wird noch heruntergeladen).

Abhängig davon, was Sie mit Ihren Daten tun, können Sie das gesamte Ergebnis einfach in sqlite ablegen, wenn es vollständig ist, es kochen und abspeichern oder es während des Lesens kochen und es am Ende abspeichern.

Ich habe eine sehr einfache Anwendung, die etwas von dem macht, was Sie auf Github wollen. Ich nenne es pfetch (paralleles Holen). Es erfasst verschiedene Seiten nach einem Zeitplan, überträgt die Ergebnisse in eine Datei und führt optional ein Skript aus, wenn die einzelnen Seiten erfolgreich abgeschlossen wurden. Es macht auch ein paar schicke Sachen wie bedingte GETs, aber es könnte trotzdem eine gute Basis für alles sein, was Sie tun.

13
Dustin

Wechseln Sie zu Multiprocessing . Es ist viel besser, skaliert gut, kann durch die Verwendung mehrerer CPUs über die Verwendung mehrerer Kerne hinausgehen, und die Schnittstelle entspricht der Verwendung des Python-Threading-Moduls.

Oder verwenden Sie, wie von ALi vorgeschlagen, einfach den Threadpooling-Mechanismus SQLAlchemy . Es wird alles automatisch für Sie erledigen und verfügt über viele zusätzliche Funktionen, um nur einige zu nennen:

  1. SQLAlchemy umfasst Dialekte für SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase und Informix. IBM hat auch einen DB2-Treiber veröffentlicht. Sie müssen Ihre Anwendung also nicht umschreiben, wenn Sie sich für SQLite entscheiden.
  2. Das Unit Of Work-System, ein zentraler Bestandteil des Object Relational Mapper (ORM) von SQLAlchemy, organisiert ausstehende Operationen zum Erstellen, Einfügen, Aktualisieren und Löschen in Warteschlangen und speichert sie alle in einem Stapel. Um dies zu erreichen, führt es eine topologische "Abhängigkeitssortierung" aller modifizierten Elemente in der Warteschlange durch, um Fremdschlüsseleinschränkungen zu berücksichtigen, und gruppiert redundante Anweisungen, in denen sie manchmal noch weiter gruppiert werden können. Dies führt zu maximaler Effizienz und Transaktionssicherheit und minimiert das Risiko von Deadlocks.
11
nosklo

Oder wenn Sie wie ich faul sind, können Sie SQLAlchemy verwenden. Es wird das Threading für Sie erledigen ( mit Thread-Local und etwas Verbindungspooling ) und die Art und Weise, wie es ausgeführt wird, ist sogar konfigurierbar .

Wenn Sie außerdem feststellen, dass die Verwendung von Sqlite für eine gleichzeitige Anwendung ein Desaster ist, müssen Sie Ihren Code nicht ändern, um MySQL, Postgres oder irgendetwas anderes zu verwenden. Sie können einfach umschalten.

7
Ali Afshar

Sie müssen session.close() nach jeder Transaktion für die Datenbank verwenden, um denselben Cursor in demselben Thread zu verwenden, der nicht in Multi-Threads den gleichen Cursor verwendet, der diesen Fehler verursacht.

2
Hazem Khaled

Ich mag Evgenys Antwort - Warteschlangen sind im Allgemeinen die beste Möglichkeit, Inter-Thread-Kommunikation zu implementieren. Der Vollständigkeit halber sind hier einige weitere Optionen aufgeführt:

  • Schließen Sie die DB-Verbindung, wenn die erzeugten Threads die Verwendung beendet haben. Dies würde Ihre OperationalError korrigieren, aber das Öffnen und Schließen von Verbindungen ist aufgrund des Leistungsaufwands im Allgemeinen ein Nein.
  • Verwenden Sie keine untergeordneten Threads. Wenn die einmal pro Sekunde dauernde Aufgabe relativ leicht ist, können Sie den Abruf und das Speichern erledigen und dann bis zum richtigen Moment schlafen. Dies ist unerwünscht, da Abruf- und Speicheroperationen> 1s dauern können und Sie den Vorteil der Multiplex-Ressourcen verlieren, die Sie mit einem Multithreading-Ansatz haben.
0
James Brady

Verwenden Sie threading.Lock ()

0
Alexandr

Scrapy scheint eine mögliche Antwort auf meine Frage zu sein. Ihre Homepage beschreibt meine genaue Aufgabe. (Obwohl ich nicht sicher bin, wie stabil der Code noch ist.)

0
RexE

Sie müssen die Parallelität für Ihr Programm entwerfen. SQLite hat klare Einschränkungen, und Sie müssen diese befolgen, siehe FAQ (auch folgende Frage).

0
iny

Ich konnte in keiner der obigen Antworten Benchmarks finden, also schrieb ich einen Test, um alles zu vergleichen.

Ich habe 3 Ansätze ausprobiert

  1. Lesen und schreiben Sie sequentiell aus der SQLite-Datenbank
  2. Verwenden eines ThreadPoolExecutor zum Lesen/Schreiben
  3. Verwenden eines ProcessPoolExecutor zum Lesen/Schreiben

Die Ergebnisse und Ergebnisse der Benchmark lauten wie folgt

  1. Sequentielle Lesevorgänge/sequentielle Schreibvorgänge funktionieren am besten
  2. Wenn Sie parallel verarbeiten müssen, verwenden Sie den ProcessPoolExecutor, um parallel zu lesen
  3. Führen Sie weder mit dem ThreadPoolExecutor noch mit dem ProcessPoolExecutor Schreibvorgänge durch, da in der Datenbank gesperrte Fehler auftreten und Sie das Einfügen des Blocks erneut versuchen müssen

Den Code und die vollständige Lösung für die Benchmarks finden Sie in meiner SO Antwort HIER Hoffe das hilft!

0
PirateApp

Ich würde mir das y_serial Python-Modul für Datenpersistenz ansehen: http://yserial.sourceforge.net

behandelt Deadlock-Probleme in Bezug auf eine einzelne SQLite-Datenbank. Wenn die Nachfrage nach Parallelität stark wird, kann die Klasse Farm vieler Datenbanken problemlos eingerichtet werden, um die Last über die stochastische Zeit zu verteilen.

Ich hoffe, das hilft Ihrem Projekt ... es sollte einfach genug sein, um es in 10 Minuten umzusetzen.

0
code43