[Python-de] threads - noch mehr Fragen

Stefan Schwarzer s.schwarzer at ndh.net
Tue Apr 30 23:05:56 EDT 2002


(Vorsicht, lange Mail)

Hallo nochmal,

> 2. Ich habe mir die Doku zum Modul threading durchgelesen, aber habe
> noch kein so rechtes Gefühl dafür, für welchen Zweck ich welche Klasse
> (Lock, RLock, Semaphore, Condition, Event) verwende. Irgendwie sind mir
> die Beschreibungen im Moment zu abstrakt, obwohl ich sonst mit
> abstrakten Beschreibungen ganz gut zurechtkomme.
> 
> Kann mir jemand Tipps geben, was ich lesen/anschauen kann, um mit den
> Konzepten vertrauter zu werden? Meine Suche mit Google (Web und Groups)
> war nicht so ergiebig.

Ich habe ein paar Informationen, wenn auch nicht Python-spezifisch, unter
http://www.embedded.com/97/fe29710.htm und
http://java.sun.com/docs/books/tutorial/essential/threads/ (besonders
http://java.sun.com/docs/books/tutorial/essential/threads/multithreaded.html)
gefunden.

Ich habe auch mal die Python-Bibliothek "durchgegrept", aber nur wenige
Verwendungen von Locks gefunden. Condition, Event und Semaphore werden
dort anscheinend gar nicht verwendet.

> 3. Ein konkretes Beispiel, mit dem ich mich gerade auseinandersetze,
> ist dieses: Ich habe einen Teil Initialisierungscode, der nur einmal
> ausgeführt werden soll, genauer, vom ersten Thread, der den Code erreicht.
> 
> Im Moment ist das etwa so implementiert, wobei eine Instanz der Klasse
> Example von mehreren Threads verwendet werden kann.
> 
> class Example:
>     def __init__(self):
>         self._is_worked_on_or_ready = False
>         self._is_ready = False
>         self._data = ...
> 
>     def _init_structure(self):
>         if self._is_worked_on_or_ready:
>             return
>         else:
>             self._is_worked_on_or_ready = True
>         # init self._data
>         ...
>         self._is_ready = True
> 
>     def use_structure(self, id):
>         # this code might be called concurrently
>         while not self._is_ready:
>             self._init_structure()
>         # do something with self._data
>         ...
>         return ...

Mein Code sieht jetzt sinngemäß so aus (einige Bemerkungen dazu
weiter unten):

class Example:
    def __init__(self):
        self._init_lock = threading.Lock()
        self._is_ready = False
        self._data = ...
        # wird vom Unittest benutzt (s. u.)
        self._thread_list = []

    def _init_structure(self):
        # nicht blockieren
        #  (leider nimmt acquire kein Keyword-Argument, sonst hätte
        #  "blocking=False" gereicht)
        available = self._init_lock.acquire(False)
        if not available:
            return
        thread_name = threading.currentThread().getName()
        self._thread_list.append(thread_name)
        # init self._data
        ...
        self._is_ready = True
        # _nicht_ freigeben!
        # self._init_lock.release()

    def use_structure(self, id):
        # this code might be called concurrently
        while not self._is_ready:
            self._init_structure()
        # do something with self._data
        ...
        return ...

Soweit ich durch Versuche feststellen konnte (Unittest folgt), tut es
das (ich hatte zeitweilig noch einige Ausgabeanweisungen im obigen
Code). Ich hatte zuerst mit einer Condition experimentiert, aber mir
ist keine sinnvolle Implementierung eingefallen. Das klassische
Beispiel für die Verwendung von Condition-Variablen sind "Producer-
Consumer"-Muster, aber use_structure ist gewissermaßen beides: erst
wird die Datenstruktur - sofern sie noch nicht initialisiert ist -
gefüllt (Producer), dann aber auch gleich verwendet (Consumer).

In einer früheren Version hatte ich self._init_lock.release()
verwendet, aber hier kam es unter (zugegebenermaßen provozierten ;-) )
Umständen zu einer Race-Condition mit zweimaliger Ausführung des
Initialisierungscodes. Das passiert anscheinend dann, wenn ein zweiter
Thread gerade am Anfang von _init_structure angekommen ist, wenn der
Lock freigegeben wird.

Ein Unittest dazu sieht übrigens etwa so aus:

    def test_thread_safety(self):
        """Test thread safety for initialization."""
        example = examplemod.Example()
        # make threads
        def thread_func():
            return example.use_structure(3)
        thread1 = threading.Thread(name='1', target=thread_func)
        thread2 = threading.Thread(name='2', target=thread_func)
        # start them
        sys.setcheckinterval(1)
        thread1.start()
        thread2.start()
        thread1.join()
        thread2.join()
        # reset to the default
        sys.setcheckinterval(10)
        self.assertEqual( example._thread_list, ['1'] )

Normalerweise baue ich keinen Testcode in den zu testenden Code ein,
sondern leite von der zu testenden Klasse ab. Das sah vorher etwa so
aus:

class DerivedExample(examplemod.Example):
    def __init__(self):
        examplemod.Example.__init__(self)
        self._thread_list = []

    def _init_structure(self):
        thread_name = threading.currentThread.getName()
        self._thread_list.append(thread_name)
        examplemod.Example._init_structure(self)

Leider konnte ich damit nicht die Race-Condition bei Weglassen der
release-Methode in _init_structure reproduzieren.

So weit meine "Lösung des Rätsels". :-) Für Hinweise auf Denkfehler
bin ich dankbar. :-)

Noch ein Hinweis: Nach meiner Erfahrung kann man eigentlich alles
automatisiert testen, man muss nur manches Mal kreativ sein. Wenn
einem nichts einfällt, helfen mitunter Mock-Objekte (s.
http://www.mockobjects.com/ ).

Viele Grüße
 Stefan



More information about the Python-de mailing list