Warum brauchen wir Schlösser für Fäden, wenn wir GIL haben?

8

Ich glaube, es ist eine dumme Frage, aber ich kann es immer noch nicht finden. Eigentlich ist es besser, es in zwei Fragen zu trennen:

1) Habe ich Recht, dass wir viele Threads haben könnten, aber wegen GIL in einem Moment wird nur ein Thread ausgeführt?

2) Wenn ja, warum brauchen wir noch Schlösser? Wir verwenden Sperren, um den Fall zu vermeiden, wenn zwei Threads versuchen, ein gemeinsames Objekt zu lesen / schreiben, weil GIL twi-Threads nicht in einem Moment ausgeführt werden können, oder?

    
Paul 16.10.2016, 16:54
quelle

3 Antworten

8

GIL schützt die Python Interals. Das heißt:

  1. Sie müssen sich keine Sorgen machen, dass etwas im Interpreter wegen Multithreading schief geht
  2. Die meisten Dinge laufen nicht wirklich parallel, weil Python-Code aufgrund von GIL
  3. sequentiell ausgeführt wird

Aber GIL schützt deinen eigenen Code nicht. Zum Beispiel, wenn Sie diesen Code haben:

%Vor%

Das wird den Wert von self.some_number lesen, some_number+1 berechnen und dann wieder in self.some_number schreiben.

Wenn Sie das in zwei Threads tun, können die Operationen (lesen, hinzufügen, schreiben) eines Threads und des anderen gemischt werden, so dass das Ergebnis falsch ist.

Dies könnte die Reihenfolge der Ausführung sein:

  1. thread1 liest self.some_number (0)
  2. thread2 liest self.some_number (0)
  3. thread1 berechnet some_number+1 (1)
  4. thread2 berechnet some_number+1 (1)
  5. thread1 schreibt 1 in self.some_number
  6. thread2 schreibt 1 in self.some_number

Sie verwenden Sperren, um diese Reihenfolge der Ausführung zu erzwingen:

  1. thread1 liest self.some_number (0)
  2. thread1 berechnet some_number+1 (1)
  3. thread1 schreibt 1 in self.some_number
  4. thread2 liest self.some_number (1)
  5. thread2 berechnet some_number+1 (2)
  6. thread2 schreibt 2 in self.some_number

BEARBEITEN: Lassen Sie uns diese Antwort mit einem Code vervollständigen, der das erklärte Verhalten zeigt:

%Vor%

Es gibt zwei Funktionen, die Inkremente implementieren. Einer verwendet Sperren und der andere nicht.

Die Funktion increment_in_x_threads implementiert die parallele Ausführung der Inkrementierungsfunktion in vielen Threads.

Wenn Sie dies jetzt mit einer ausreichend großen Anzahl von Threads ausführen, wird fast sicher ein Fehler auftreten:

%Vor%

In meinem Fall hat es gedruckt:

%Vor%

Also ohne Sperren gab es viele Fehler (33% der Inkremente fehlgeschlagen). Auf der anderen Seite, mit Sperren war es 20 mal langsamer.

Natürlich sind beide Zahlen aufgebläht, weil ich 70 Threads benutzt habe, aber das zeigt die allgemeine Idee.

    
zvone 16.10.2016, 17:07
quelle
3

Zu jedem Zeitpunkt, ja, nur ein Thread führt Python-Code aus (andere Threads können einige IO, NumPy, was auch immer ausführen). Das ist meistens richtig. Dies ist jedoch trivialerweise bei jedem Einzelprozessorsystem der Fall, und dennoch benötigen Benutzer immer noch Sperren für Einzelprozessorsysteme.

Sehen Sie sich den folgenden Code an:

%Vor%

Mit einem Thread ist alles in Ordnung. Bei zwei Threads erhalten Sie möglicherweise eine Ausnahme von queue.pop() , weil der andere Thread zuerst queue.pop() für das letzte Element genannt hat. Also müsstest du irgendwie damit umgehen. Die Verwendung einer Sperre ist eine einfache Lösung. Sie können auch eine ordnungsgemäße gleichzeitige Warteschlange verwenden (wie im Modul queue ). Wenn Sie jedoch in das Modul queue schauen, werden Sie feststellen, dass das Objekt Queue ein threading.Lock() enthält. So oder so Sie Sperren verwenden.

Es ist ein häufiger Anfängerfehler, Multithreading-Code ohne die erforderlichen Sperren zu schreiben. Sie schauen sich Code an und denken, "das wird gut funktionieren" und dann viele Stunden später herausfinden, dass etwas wirklich Bizarres passiert ist, weil die Threads nicht richtig synchronisiert wurden.

Kurz gesagt, gibt es in einem Multithread-Programm viele Stellen, an denen Sie verhindern müssen, dass ein anderer Thread eine Struktur ändert, bis Sie einige Änderungen vorgenommen haben. Auf diese Weise können Sie die Invarianten Ihrer Daten beibehalten. Wenn Sie keine Invarianten pflegen können, ist es im Grunde unmöglich, einen korrekten Code zu schreiben.

Oder geben Sie den kürzesten Weg ein: "Sie brauchen keine Sperren, wenn es Ihnen egal ist, ob Ihr Code korrekt ist."

    
Dietrich Epp 16.10.2016 17:08
quelle
2

Die GIL verhindert die gleichzeitige Ausführung mehrerer Threads, aber nicht in allen Situationen.

Die GIL wird vorübergehend während der von Threads ausgeführten E / A-Operationen freigegeben. Das bedeutet, dass mehrere Threads gleichzeitig ausgeführt werden können. Das ist ein Grund, warum Sie immer noch Sperren benötigen.

Ich weiß nicht, wo ich diese Referenz gefunden habe ... in einem Video oder so - schwer zu lesen, aber Sie können selbst weiter nachforschen

    
vlad-ardelean 16.10.2016 16:58
quelle

Tags und Links