2009-07-02 9 views
14

czytałem this question (który nie trzeba czytać, bo będę kopiować to, co jest tam ... Po prostu chciałem dać ci pokazać moją inspirację) ...Czy modyfikuje zmienną klasy w python threadsafe?

Więc, jeśli mam klasy, która się liczy ilu przypadków powstały:

class Foo(object): 
    instance_count = 0 
    def __init__(self): 
    Foo.instance_count += 1 

Moje pytanie brzmi, czy mogę tworzyć obiekty Foo w wielu wątków, jest instance_count będzie prawidłowa? Czy zmienne klasy można bezpiecznie modyfikować z wielu wątków?

Odpowiedz

21

Nie jest to wątek bezpieczny nawet w CPython. Spróbuj to zobaczyć na własne oczy:

import threading 

class Foo(object): 
    instance_count = 0 

def inc_by(n): 
    for i in xrange(n): 
     Foo.instance_count += 1 

threads = [threading.Thread(target=inc_by, args=(100000,)) for thread_nr in xrange(100)] 
for thread in threads: thread.start() 
for thread in threads: thread.join() 

print(Foo.instance_count) # Expected 10M for threadsafe ops, I get around 5M 

Powodem jest to, że podczas INPLACE_ADD jest atomowy pod GIL, atrybut jest nadal załadowany i przechowywania (patrz dis.dis (Foo .__ init__)). Użyj blokady do serializacji dostępu do zmiennej klasy:

Foo.lock = threading.Lock() 

def interlocked_inc(n): 
    for i in xrange(n): 
     with Foo.lock: 
      Foo.instance_count += 1 

threads = [threading.Thread(target=interlocked_inc, args=(100000,)) for thread_nr in xrange(100)] 
for thread in threads: thread.start() 
for thread in threads: thread.join() 

print(Foo.instance_count) 
+0

Wierzę w twój drugi przykład, że chcesz, aby cel wątku był sprzężony_inc zamiast inc_by. – tgray

+0

Dzięki, poprawione. Zbyt liberalne programowanie kopiowania i wklejania czasami mnie pochłania. –

+0

Dziękuję Ants Aasma :-). Jest tak, jak podejrzewałem. Dziękuję za udowodnienie mi tego. Jak wskazuje tgray, twój drugi cel powinien być blokowany_inc. Ale kiedy to zmienisz ... wygląda nieskazitelnie. – Tom

-4

Powiedziałbym, że jest bezpieczny dla wątków, przynajmniej na podstawie implementacji CPython. GIL sprawi, że wszystkie twoje "wątki" będą uruchamiane sekwencyjnie, więc nie będą mogły zepsuć twojej liczby referencyjnej.

+2

Czy Foo.instance_count + = 1 i atomowa jednostka pracy? –

+0

Może nie rozumiem, jak działa GIL ... ale wciąż go nie widzę. Nie można Thread1 odczytać instance_count. Następnie wątek1 zatrzymuje się. Wątek2 odczytuje instance_count, a następnie zatrzymuje się. Wątek1 modyfikuje i zapisuje. Wątek2 pisze. Więc tracisz inkrement? W jaki sposób GIL zapewnia wątek przebiegający przez całą operację + =? – Tom

+0

Ha, w zasadzie pytałem, co Sam Saffron zapytał tuż przede mną. – Tom

8

Nie jest to bezpieczne dla wątków. Kilka dni temu spotkałem się z podobnym problemem i postanowiłem wdrożyć zamek dzięki dekoratorowi. Zaletą jest to, że kod jest czytelny:

 
def threadsafe_function(fn): 
    """decorator making sure that the decorated function is thread safe""" 
    lock = threading.Lock() 
    def new(*args, **kwargs): 
     lock.acquire() 
     try: 
      r = fn(*args, **kwargs) 
     except Exception as e: 
      raise e 
     finally: 
      lock.release() 
     return r 
    return new 

class X: 
    var = 0 

    @threadsafe_function  
    def inc_var(self): 
     X.var += 1  
     return X.var 
+0

nie na temat, ale czy można usunąć dwa wywołania lock.release() do sekcji "else:" po obsłudze wyjątku? –

+0

Masz na myśli w sekcji końcowej? Zrobienie tego w innym przypadku nie zostanie zwolnione, gdy zostanie zgłoszony wyjątek – luc

+0

ah tak, właśnie o to mi chodziło. dzięki! –

Powiązane problemy