2014-11-11 14 views
7

Ich bin mir bewusst, dass Python-Threads Bytecode nur einzeln ausführen können, warum also würde die threading-Bibliothek Sperren bereitstellen? Ich nehme an, dass Race-Bedingungen nicht auftreten können, wenn nur ein Thread gleichzeitig ausgeführt wird.Warum stellt Python Sperrmechanismen zur Verfügung, wenn es einer GIL unterliegt?

Die Bibliothek bietet Sperren, Bedingungen und Semaphore. Ist dies der einzige Zweck, um die Ausführung zu synchronisieren?

Update:

ich ein kleines Experiment durchgeführt:

from threading import Thread 
from multiprocessing import Process 

num = 0 

def f(): 
    global num 
    num += 1 

def thread(func): 
    # return Process(target=func) 
    return Thread(target=func) 


if __name__ == '__main__': 
    t_list = [] 
    for i in xrange(1, 100000): 
     t = thread(f) 
     t.start() 
     t_list.append(t) 

    for t in t_list: 
     t.join() 

    print num 

Grundsätzlich sollte ich von 1. Das zurückgegebene Ergebnis 100k Threads und erhöht begonnen haben, war 99993.

a) Wie kann das Ergebnis 99999 nicht sein, wenn es eine GIL-Synchronisierung gibt und Rennbedingungen vermeidet? b) Ist es überhaupt möglich, 100k OS-Threads zu starten?

Update 2, nach Antworten zu sehen:

Wenn die GIL nicht wirklich bieten eine Möglichkeit, eine einfache Bedienung wie Inkrementieren atomar durchzuführen, was ist der Zweck es dort zu haben? Es hilft nicht bei bösen Nebenläufigkeitsproblemen, also warum wurde es eingesetzt? Ich habe Anwendungsfälle für C-Extensions gehört, kann jemand dies exemplarisch darstellen?

+1

Die GIL schützt den Python-Interpreter vor Nebenläufigkeitsproblemen und nicht vor Code, den Sie schreiben. Es ist wirklich ein Implementierungsdetail von CPython, und Sie sollten sich nicht auf sein Verhalten in Ihrem eigenen Code verlassen, obwohl es wahrscheinlich nicht bald wieder verschwinden wird. – dano

Antwort

8

Die GIL synchronisiert Bytecode-Operationen. Nur ein Byte-Code kann gleichzeitig ausgeführt werden. Aber wenn Sie eine Operation haben, die mehr als einen Bytecode benötigt, können Sie Threads zwischen den Bytecodes wechseln. Wenn Sie eine atomare Operation benötigen, müssen Sie über die GIL hinaus synchronisieren.

zum Beispiel eine ganze Zahl erhöht wird ist nicht eine einzige Bytecode:

>>> def f(): 
... global num 
... num += 1 
... 
>>> dis.dis(f) 
    3   0 LOAD_GLOBAL    0 (num) 
       3 LOAD_CONST    1 (1) 
       6 INPLACE_ADD 
       7 STORE_GLOBAL    0 (num) 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE 

Hier dauerte es vier Bytecode num += 1 zu implementieren. Die GIL wird nicht sicherstellen, dass x atomar inkrementiert wird. Ihr Test zeigt das Problem: Sie haben Updates verloren, weil die Threads zwischen LOAD_GLOBAL und STORE_GLOBAL gewechselt haben.

Der Zweck der GIL besteht darin, sicherzustellen, dass die Referenzzählungen auf Python-Objekten atomar inkrementiert und dekrementiert werden. Es soll Ihnen nicht mit Ihren eigenen Datenstrukturen helfen.

3

Das native Threading von Python funktioniert auf Bytecode-Ebene. Das heißt, nach jedem Bytecode (naja, ich glaube tatsächlich, dass die Anzahl der Bytecodes konfigurierbar ist), kann ein Thread die Steuerung für einen anderen Thread übernehmen.

Jede Operation auf einer freigegebenen Ressource, die kein einzelner Bytecode ist, benötigt eine Sperre. Und selbst wenn eine bestimmte Operation in einer bestimmten Version von CPython ein einzelner Bytecode ist, ist das möglicherweise nicht in jeder Version jedes Interpreters der Fall, also sollten Sie besser eine Sperre verwenden.

Derselbe Grund, warum Sie Sperren wirklich beginnen müssen, außer auf VM-Ebene und nicht auf Hardware-Ebene.