Frage

Ich mag für Experimente ein nicht-Thread-sicheres Stück Code erstellen, und das sind die Funktionen, die zwei Threads Anruf werden.

c = 0

def increment():
  c += 1

def decrement():
  c -= 1

Ist dieser Code Thread-sicher?

Wenn nicht, kann ich verstehen, warum es nicht sicher ist, fädeln, und welche Art von Aussagen in der Regel zu nicht-Thread-sicheren Betrieb führen.

Wenn es Thread-sicher, wie kann ich es explizit nicht-Thread-sicher zu machen?

War es hilfreich?

Lösung

Single Opcodes sind Thread-sicher wegen der GIL aber nichts anderes:

import time
class something(object):
    def __init__(self,c):
        self.c=c
    def inc(self):
        new = self.c+1 
        # if the thread is interrupted by another inc() call its result is wrong
        time.sleep(0.001) # sleep makes the os continue another thread
        self.c = new


x = something(0)
import threading

for _ in range(10000):
    threading.Thread(target=x.inc).start()

print x.c # ~900 here, instead of 10000

Alle Ressource von mehreren Threads gemeinsam genutzt muss eine Sperre haben.

Andere Tipps

Nein, ist dieser Code absolut, nachweislich nicht THREAD.

import threading

i = 0

def test():
    global i
    for x in range(100000):
        i += 1

threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
    t.start()

for t in threads:
    t.join()

assert i == 1000000, i

nicht konsequent.

i + = 1 Entschlüsse bis vier OP-Codes: Last i, Last 1, fügen Sie die zwei, und speichern Sie es auf i zurück. Der Python-Interpreter schaltet aktive Threads (indem die GIL von einem Fadenfreigabe so ein anderen Thread es haben kann) alle 100 Opcodes. (Beide sind Implementierungsdetails). Die Race-Bedingung tritt auf, wenn das 100-Opcode Preemption zwischen Laden und Speichern der Fall ist, so dass ein anderer Thread, den Zähler zu inkrementieren beginnen. Wenn es zurück zu dem suspendierten Thread bekommt, fährt er mit dem alten Wert von „i“ und rückgängig macht die Inkremente laufen durch andere Threads in der Zwischenzeit.

es macht THREAD ist einfach; fügen Sie ein Schloss:

#!/usr/bin/python
import threading
i = 0
i_lock = threading.Lock()

def test():
    global i
    i_lock.acquire()
    try:
        for x in range(100000):
            i += 1
    finally:
        i_lock.release()

threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
    t.start()

for t in threads:
    t.join()

assert i == 1000000, i

(Anmerkung:. Sie würden global c in jeder Funktion benötigen, um Ihren Code Arbeit zu machen)

  

Ist dieser Code Thread-sicher?

Nein. Nur ein einziger Bytecode-Befehl ‚Atom‘ in CPython ist, und ein += in einem einzigen Opcode nicht führen kann, auch wenn die beteiligten Werte einfache, ganze Zahlen:

>>> c= 0
>>> def inc():
...     global c
...     c+= 1

>>> import dis
>>> dis.dis(inc)

  3           0 LOAD_GLOBAL              0 (c)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD         
              7 STORE_GLOBAL             0 (c)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

So einen Thread-Index 6 mit c bekommen konnte und 1 geladen wird, geben die GIL und lassen einen weiteren Thread in, die eine inc und schläft ausführt, die GIL zu den ersten Thread zurückkehren, die jetzt den falschen Wert hat.

Auf jeden Fall, was atomar ist, ist eine Implementierung Detail, die Sie nicht verlassen sollte. Bytecode kann in zukünftigen Versionen von CPython ändern, und die Ergebnisse werden in anderen Implementierungen von Python ganz anders sein, die auf einem GIL verlassen Sie sich nicht. Wenn Sie Sicherheitsfaden braucht, benötigen Sie einen Verriegelungsmechanismus.

Um sicherzustellen, dass ich empfehlen, eine Sperre zu verwenden:

import threading

class ThreadSafeCounter():
    def __init__(self):
        self.lock = threading.Lock()
        self.counter=0

    def increment(self):
        with self.lock:
            self.counter+=1


    def decrement(self):
        with self.lock:
            self.counter-=1

Die synchronisierte Dekorateur kann auch helfen, den Code zu halten, leicht zu lesen.

Es ist leicht zu beweisen, dass der Code ist nicht Thread-sicher . Sie können mit einem Schlaf in den kritischen Teilen (dies einfach simuliert eine langsame CPU) die Wahrscheinlichkeit des Sehens der Race-Bedingung erhöhen. Allerdings, wenn Sie den Code für lange genug laufen sollten Sie die Race-Bedingung sehen schließlich unabhängig.

from time import sleep
c = 0

def increment():
  global c
  c_ = c
  sleep(0.1)
  c = c_ + 1

def decrement():
  global c
  c_ = c
  sleep(0.1)
  c  = c_ - 1

Kurze Antwort: no.

Lange Antwort:. Im Allgemeinen nicht

Während CPython GIL Einzel Opcodes thread-safe macht, ist dies kein allgemeines Verhalten. Sie können nicht, dass auch einfache Operationen übernehmen, wie eine Addition eine atomare Anweisung ist. Der Zusatz kann nur die Hälfte getan werden, wenn ein anderer Thread ausgeführt wird.

Und sobald Ihre Funktionen Zugriff auf eine Variable in mehr als ein Opcode, Ihre Thread-Sicherheit ist weg. Sie können Thread-Sicherheit erzeugen, wenn Sie Ihre Funktion Körper in Schlösser . Aber beachten Sie, dass Sperren rechnerisch kostspielig sein kann und Deadlocks erzeugen.

Wenn Sie wirklich wollen, um Ihren Code machen nicht Thread-sicher, und haben gute Chancen, „schlechte“ Sachen tatsächlich passieren, ohne dass Sie wie zehntausend Mal versuchen (oder eine Zeit, wenn Sie echten nicht "schlechte" Sachen mögen, können passieren) 'Jitter' Code mit expliziten Betten:

def íncrement():
    global c
    x = c
    from time import sleep
    sleep(0.1)
    c = x + 1

Sind Sie sicher, dass die Funktionen erhöhen und ohne Fehler ausführen Dekrement?

ich denke, es sollte eine UnboundLocalError erhöhen, weil Sie explizit Python haben zu sagen, dass Sie die globale Variable verwenden möchten namens ‚c‘.

So ändern Schritt (auch Dekrement) auf die folgenden:

def increment():
    global c
    c += 1

Ich denke, wie Ihr Code Thread unsicher. Dieser Artikel über Thread-Synchronisierungsmechanismen in Python sehr hilfreich sein kann.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top