2010-04-29 5 views
5

Einige Diskussion in einer anderen Frage hat mich ermutigt, Fälle besser zu verstehen, in denen Sperren in Multithread-Python-Programmen erforderlich ist.Gibt es Fälle, in denen Python-Threads den gemeinsamen Status sicher manipulieren können?

Pro this Artikel über Threading in Python, habe ich mehrere solide, testbare Beispiele für Fallstricke, die auftreten können, wenn mehrere Threads auf den gemeinsamen Zustand zugreifen. Die beispielhafte Race-Bedingung, die auf dieser Seite bereitgestellt wird, umfasst Rennen zwischen Threads, die eine in einem Wörterbuch gespeicherte gemeinsame Variable lesen und bearbeiten. Ich denke, dass der Fall für ein Rennen hier sehr offensichtlich ist und zum Glück hervorragend überprüfbar ist.

Ich konnte jedoch mit atomaren Operationen wie Listenanfügungen oder variablen Inkrementen keine Wettlaufsituation hervorrufen. Dieser Test versucht, erschöpfend ein solches Rennen zu demonstrieren:

from threading import Thread, Lock 
import operator 

def contains_all_ints(l, n): 
    l.sort() 
    for i in xrange(0, n): 
     if l[i] != i: 
      return False 
    return True 

def test(ntests): 
    results = [] 
    threads = [] 
    def lockless_append(i): 
     results.append(i) 
    for i in xrange(0, ntests): 
     threads.append(Thread(target=lockless_append, args=(i,))) 
     threads[i].start() 
    for i in xrange(0, ntests): 
     threads[i].join() 
    if len(results) != ntests or not contains_all_ints(results, ntests): 
     return False 
    else: 
     return True 

for i in range(0,100): 
    if test(100000): 
     print "OK", i 
    else: 
     print "appending to a list without locks *is* unsafe" 
     exit() 

ich den Test oben ohne Fehler (100x 100k multithreaded Appends) ausgeführt haben. Kann jemand es zum Scheitern bringen? Gibt es eine andere Klasse von Objekten, die durch atomare, inkrementelle Modifikation durch Threads zum Fehlverhalten gebracht werden können?

Wenden sich diese implizit 'atomaren' Semantiken auf andere Operationen in Python an? Ist das direkt mit der GIL verbunden?

+0

Testen ist keine gültige Methode zum Nachweis der Korrektheit in gleichzeitigen Anwendungen. Es ist sehr einfach für einen bestimmten Test, die Ausführung in einer unerwartet vorhersehbaren Art und Weise zu verschachteln, die niemals das Problem auslösen würde, jedoch die kleinsten Änderungen am Code (d. H.wenn es in eine reale Situation erweitert wird) könnte sofort den Fehler aufzeigen. Die Korrektheit der gleichzeitigen Software sollte bewiesen, nicht getestet werden. – Kylotan

Antwort

7

Anhängen an eine Liste ist threadsicher, ja. Sie können nur an eine Liste anhängen, während Sie die GIL halten, und die Liste sorgt dafür, dass die GIL während der Operation append nicht freigegeben wird (was schließlich eine recht einfache Operation ist.) Die Order, in der verschiedene Thread-Append-Operationen gehen Through steht natürlich zur Verfügung, aber sie werden alle streng serialisierte Operationen sein, weil die GIL niemals während eines Appends freigegeben wird.

Das gleiche gilt nicht unbedingt für andere Operationen. Viele Operationen in Python können dazu führen, dass beliebiger Python-Code ausgeführt wird, was wiederum dazu führen kann, dass die GIL freigegeben wird. Zum Beispiel ist i += 1 drei verschiedene Operationen, "get i", "add 1 to it" und "speichern Sie es in i". "1 hinzufügen" würde (in diesem Fall) in it.__iadd__(1) übersetzen, die gehen und tun können

Python-Objekte selbst schützen ihren eigenen internen Status - Dicts werden nicht durch zwei verschiedene Threads beschädigt, die versuchen, Elemente in ihnen zu setzen.Aber wenn die Daten im Dict intern konsistent sein sollen, auch nicht die dict noch die GIL tut etwas, das zu schützen, außer (in üblichen Faden Mode), indem es weniger wahrscheinlich, aber noch möglich Dinge am Ende anders als man dachte.

1

in CPython, Thread-Wechsel Die Ausführung erfolgt, wenn sys.getcheckinteval() bycodes ausgeführt wurde. Daher kann ein Kontextwechsel niemals während der Ausführung eines einzelnen Bytecodes auftreten, und Operationen, die als einzelner Bytecode codiert sind, sind inhärent atomar und threadsicher, es sei denn, dieser Bytecode führt anderen Python-Code aus oder ruft C-Code auf, der die GIL freigibt. Die meisten Operationen für die integrierten Sammlungstypen (dict, list usw.) fallen in die Kategorie "inhärent Threadsafe".

Dies ist jedoch ein Implementierungsdetail, das spezifisch für die C-Implementierung von Python ist, und sollte nicht verlässlich sein. Andere Versionen von Python (Jython, IronPython, PyPy usw.) verhalten sich möglicherweise nicht auf die gleiche Weise. Es gibt auch keine Garantie, dass zukünftige Versionen von CPython dieses Verhalten beibehalten.

+0

Dies folgte meiner Intuition, was geschah, aber ich hätte nie gedacht, die anderen Implementierungen zu untersuchen. Ich verwende nur CPython. Ich bin der Meinung, dass, um Code gegen alternative Implementierungen zukunftssicher zu machen, dieses Detail nicht verlässlich sein sollte. –

+0

Die Bytecode-Unterscheidung ist meistens nicht sehr interessant, weil fast alle Bytecodes das Potenzial haben, mehr Python-Code auszuführen, und einige, die nicht die Möglichkeit haben, die GIL selbst zu veröffentlichen. 'list.append()' zum Beispiel ist kein Bytecode und die eigentliche 'append'-Arbeit wird durch den' CALL_FUNCTION'-Opcode ausgeführt, der * extrem wahrscheinlich * ist, um mehr Code auszuführen :-) –

Verwandte Themen