• Keine Ergebnisse gefunden

Acht-Damen-Problem

Im Dokument Programmiertechniken (NF) (Seite 86-0)

8. Backtracking 83

8.2. Acht-Damen-Problem

Beim Acht-Damen-Problem m¨ussen acht Damen auf einem Schachbrett so positioniert werden, dass sich keine zwei (oder mehr) Damen gegenseitig schlagen k¨onnen. Auch diese Problem werden wir mittels Backtracking l¨osen und nach und nach Damen auf m¨oglichen Positionen zum Schachbrett hinzuf¨ugen.

Beim Schach kann jede Dame beliebig viele Schritte in eine beliebige Richtung sowohl waagerecht, senkrecht als auch diagonal gehen. F¨ur eine m¨ogliche L¨osung k¨onnen wir uns direkt ¨uberlegen, dass in jeder Spalte und jeder Zeile maximal eine Dame platziert werden darf. Das Schlagen in diagonaler Richtung zu verhindern ist etwas komplizierter, da es auf

die Entfernung der Damen voneinander ankommt, auf welcher H¨ohe sie sicher sind und auf welcher nicht. Wir werden dies aber ebenfalls Elegant ausdr¨ucken k¨onnen.

In der Kodierung des Problems k¨onnen wir sehr einfach nur die Situationen repr¨asentieren, in denen sich Damen weder waagerecht noch senkrecht schlagen k¨onnen. Dies ist sinnvoll, da wir durch diese Kodierung den Suchraum stark verkleinern k¨onnen und wir nicht fast 64 Positionen f¨ur jede Dame betrachten m¨ussen.

Eine m¨ogliche Anordnung der Damen k¨onnen wir als Listen kodieren, bei welchen die Eintr¨age den Spalten des Schachfeldes entsprechen. Als Eintr¨age verwenden wir Zahlen von Null bis Sieben. Kommen in der Liste keine zwei gleichen Zahlen vor, entspricht dies einer Platzierung der Damen, so dass sie sich weder waagerecht noch senkrecht schlagen k¨onnen. Es ist also nur noch n¨otig zu ¨uberpr¨ufen ob sich zwei Damen diagonal schlagen k¨onnen. Durch diese Darstellung wird der Suchraum sehr stark eingeschr¨ankt, im Vergleich zur Platzierung der (acht) Damen auf beliebigen Feldern des Schachfeldes.

Eine L¨osung f¨ur das 4-Damen-Problem, als der Positionierung von vier Damen auf einem 4x4-Schachbrett sieht wie folgt aus:

q q q q

Dieses Feld w¨urden wir in unserer Darstellung als die Liste [1,3,0,2]. die Dame der ersten Spalte steht in Zeile 1, die Dame der zweiten spalte in Zeile 3, die der dritten Spalte in Zeile 0 und die in der dirtten und letzten Spalte in Zeile 2.

Man sieht, dass eine Positionierung der aller Damen auf einenn×n-Feld immer eine Liste der L¨angenist und genau die die Zahlen von0 bisn−1enth¨alt.

Beispiel: L¨osung Vier-Damen-Problem auf einem 4x4-Spielfeld mittels Backtracking

Wir beginnen mit dem leeren Schachbrett. Un d f¨ullen diese nach und nach wie folgt:

q q

Die Dame in Spalte 0 kommt auf Position 0, die n¨achste kann dann nicht auf Position 0 und nicht auf Position 1 stehen (hier w¨urde sie ja diagonal von der ersten Dame geschlagen werden), also kommt sie auf Position 2. F¨ur die Dame in Spalte 2 gibt es dann aber schon keine sichere Position mehr. Wir nehmen die Entscheidung die Dame in Spalte 1 auf Position 2 zu setzen also zur¨uck.

q q

q

Als n¨achste Alternative probieren wir die Dame in Spalte 1 auf Po-sition 3 zu setzen. Jetzt kann die Dame in Spalte 2 auf PoPo-sition 1 stehen. Dann gibt es aber f¨ur die letzte Spalte keine M¨oglichkeit mehr, die letzte Dame zu positionieren. Da wir keine weiteren Al-ternativen f¨ur Positionierungen in spalte 2 und 1 haben, nehmen wir also alle Positionierungen wieder zur¨uck, bis das Feld wieder leer ist.

q q q q

Nun beginnen wir mit der n¨achsten M¨oglichkeit, eine Dame in Spalte 0 zu Platzieren. Wir setzen sie in Zeile 1. Dann kommt f¨ur Spalte 1 nur Zeile 3 in Frage. Danach dann Zeile 0 f¨ur Spalte 2 und abschließend Zeile 2 f¨ur Spalte 3. Wir habe eine eine L¨osung gefunden und k¨onnen diese ausgeben.

Somit wurde das Problem intuitiv mit Hilfe von Backtracking gel¨ost. Dies ging schnell, da die Anzahl der M¨oglichkeiten begrenzt war und direkt beim Setzen der n¨achsten Dame darauf geachtet wurde, nur richtige Positionen in Betracht zu ziehen. Außerdem haben wir nachdem eine L¨osung gefunden wurde, sofort mit dem Suchen aufgeh¨ort, obwohl es noch eine weitere L¨osung gibt. Der folgende Baum stellt die m¨oglichen Wege dar, die durch Backtracking ¨uberpr¨uft werden. Die Spielfelder werden hier verk¨urzt als Liste der Spalten dargestellt, wobei der Eintrag der Position der Dame in dieser Spalte darstellt. Es wird beim Wurzelknoten mit der leeren Liste gestartet, und dann wird in jeder Ebene eine Dame hinzugenommen, also eine weitere Entscheidung getroffen. Um die erste L¨osung zu finden, mussten wir nur den Teilbaum mit der[0]und den mit der[1]durchlaufen.

[]

Zum Realisieren einer Backtracking-L¨osung f¨ur das 8-Damen-Problem k¨onnen wir die glei-chen Schritte vornehmen, wie beim Euler-Pfad. Wir wandeln den Algorithmus aber so, dass nur die erste L¨osung ausgegeben wird. Hierzu verwenden wir einen booleschen R¨ uckgabe-wert, welcher auch die Schleifen auf h¨oheren Ebenen beenden kann. Die f¨unf Schritte des

Backtracking-Algorithmus sehen f¨ur das 8-Damen-Problem wie folgt aus:

1. Bin ich schon fertig?

Zuerst wird gepr¨uft, ob bereits alle Entscheidungen getroffen wurden, also in diesem Fall, ob die richtige Anzahl von Damen auf dem Spielfeld steht. Ist dies der Fall geben wir das aktuelle Spielfeld als L¨osung aus. Außerdem geben wir True zur¨uck, was verhindern wird, dass weitere L¨osungen berechnet werden.

2. Platziere die noch freien Damen in der n¨achsten Spalte und erhalte so die m¨oglichen n¨achsten Zust¨ande. Hierzu definieren wir eine Hilfsfunktion place next , welche uns eine Liste der Nachfolge-Spielfelder berechnet.

3. Iteriere ¨uber alle m¨oglichen Nachfolge-Spielfelder. Da wir nur die erste L¨osung aus-geben wollen, verwenden wir hier einewhile-Schleife und brechen auch ab, wenn wir in der Rekursion erfolgreich waren (boolescher R¨uckgabewert True).

4. ¨Uberpr¨ufe den n¨achsten Zustand, ob es sinnvoll ist, mit ihm weiter zu machen. Hierzu schauen, wir wieder mit einer Hilfsfunktion, ob sich zwei Damen diagonal schlagen k¨onnen.

5. Nur, wenn dies nicht der Fall ist, rufen wir unsere Funktion rekursiv mit dem Nachfolge-Feld auf. Der boolesche R¨uckgabewert zeigt an, ob wir in dieser Un-terberechnung eine L¨osung gefunden haben.

Diese f¨unf Schritte k¨onnen wir in dem folgenden Algorithmus in Python umsetzen:

d e f s o l v e ( q u e e n s , s i z e ) :

i f i s c o m p l e t e ( q u e e n s , s i z e ) : # 1 . p r i n t q u e e n s ( q u e e n s )

r e t u r n True e l s e :

n e x t q u e e n s = p l a c e n e x t ( q u e e n s , s i z e ) # 2 . i = 0

s o l v a b l e = F a l s e

w h i l e not s o l v a b l e and i < l e n( n e x t q u e e n s ) : # 3 . i f d i a g s a f e ( n e x t q u e e n s [ i ] ) : # 4 . s o l v a b l e = s o l v e ( n e x t q u e e n s [ i ] , s i z e ) # 5 . i = i + 1

Durch die Verwendung der Variablen solvable in Kombination mit der while-Schleife, ist es m¨oglich, nur die erste L¨osung auszugeben. Man erkennt die gleiche Struktur, wie bei der suche nach einem Euler-Pfad. dies liegt insbesondere daran, dass wir wichtige Hilfsfunktionen ausgelagert habe. Es bleibt also noch diese zu definieren.

Wir starten mit der Funktion is complete , welche erkennt, wenn die Liste komplett ist.

Hierf¨ur reichen wir den Gr¨oßenparameter size bis zu dieser Funktion durch.

d e f i s c o m p l e t e ( l , s i z e ) : r e t u r n l e n( l ) == s i z e

Etwas komplizierter ist es zu ¨uberpr¨ufen, ob sich zwei oder mehr Damen diagonal schlagen k¨onnen.

d e f d i a g s a f e ( q u e e n s ) : s a f e = True

f o r i i n range(l e n( q u e e n s ) ) :

f o r j i n range( i +1 ,l e n( q u e e n s ) ) :

i f abs( q u e e n s [ i ] − q u e e n s [ j ] ) == j − i : s a f e = F a l s e

Hierf¨ur verwenden wir zwei for-Schleifen und vergleichen die Diferenz der Werte (entspricht dem Zeilenunterschied) und die Differenz der Indizes (entspricht dem Spaltenunterschied).

Sind diese identisch k¨onnen sich zwei Damen schlagen. Um nicht nur die Diagonalen von links oben nach rechts unten zu betrachten, k¨onnen wir außerdem noch den Betrag (abs) f¨ur die Zeilenwerte verwenden. Um diesen Code nach zu vollziehen, sollte man ihn auf eine paar kleine Beispiele anwenden.

Diese Funktion ist noch nicht optimal und kann mit zwei Ideen optimiert werden. Zum einen kann man diefor-Schleifen durch while-Schleifen ersetzen, welche auch schon enden, wenn safe auf False gesetzt wurde. Außerdem reicht es aus immer nur f¨ur die letzte hinzugef¨ugte Dame zu ¨uberpr¨ufen, ob diese auf einer Diagonalen mit einer der anderen Damen steht. F¨ur die Damen in den Spalten davor, k¨onnen wir auf Grund des Bracktrackings ja sicher sein, dass sie sich nicht gegenseitig schlagen k¨onnen. Diese Optimierungen k¨onnen ebenfalls als Ubung vorgenommen werden.¨

Als letzte Hilfsfunktion m¨ussen wir noch die Funktion place next realisieren:

d e f p l a c e n e x t ( q u e e n s , s i z e ) : n e x t q u e e n s = [ ]

f o r i i n range( s i z e ) : i f not i i n q u e e n s :

n e x t q u e e n s . append ( q u e e n s + [ i ] ) r e t u r n n e x t q u e e n s

Man beachte, dass wir eine Liste von Listen berechnen. Hierzu f¨ugen wir an die aktuelle queens-Liste eine Postion i hinzu, falls diese noch nicht in queens vorkam. F¨ur unser 4×-Feld erg¨abe sich z.B.

p l a c e n e x t ( [ 1 , 3 ] , 4 ) l i e f e r t [ [ 1 , 3 , 0 ] , [ 1 , 3 , 2 ] ]

Es werden also nur die beiden noch nicht verwendeten Positionen 0 und 2 hinzugef¨ugt.

Man beachte, dass next queens eine Liste von Feldern (also Listen ist) und mit append um ein weiteres Element (das n¨achste Feld) erweitert wird.

Das gesamte Programm erg¨anzt um eine ¨ubersichtliche Ausgabe steht im iLearn zur Verf¨ugung und wird auch in der Vorlesung ausf¨uhrlich diskutiert.

Die L¨osung durch Backtracking kann im Worstcase die gleiche Laufzeit wie eine L¨osung mittels Aufz¨ahlen und ¨Uberpr¨ufen haben, aber meistens ist sie deutlich schneller. Das Vier-Damen-Problem wird z.B. mit insgesamt 9 ¨Uberpr¨ufungen gel¨ost im Gegensatz zu den 256 Uberpr¨¨ ufungen bei Aufz¨ahlen und ¨Uberpr¨ufen.

Teil III.

Nebenl¨ aufigkeit

9. Threads

Bisher haben wir uns nur mit Programmen besch¨aftigt, die genau eine Sache gleichzeitig tun und zwar in einer ganz klar definierten Reihenfolge. Komplexere Programme, insbe-sondere Betriebssysteme, berechnen jedoch mehrere Dinge so, dass es f¨ur den Benutzer scheint, als w¨urden sie gleichzeitig passieren. Dies ist wichtig, damit Systeme sich reak-tiv verhalten. Der Benutzer oder andere Systeme also das Gef¨uhl haben, das System steht sofort zur Verf¨ugung, auch wenn es gerade noch damit besch¨aftigt ist, andere Dinge zu Be-rechnen. Gut Beispiele hierf¨ur sind graphische Benutzerobefl¨achen, welche auch dann noch reagieren, wenn ein Programm, z.B. eine Textverarbeitung arbeitet, rechnet oder druckt.

Ein anderes Beispiel sind Web-Server, welche gleichzeitig viele Anfragen bearbeiten und die entsprechenden Web-Seiten ausliefern.

Arbeiten mehrere Programme oder Anwendungen gleichzeitig, nennt man diesNebenl¨ aufig-keit. F¨ur unsere Python-Programme k¨onnen wir uns vorstellen, dass mehrere Python-Programme gleichzeitig ablaufen k¨onnen und innerhalb eines Python-Programms weitere Programme gestartet werden k¨onnen und sogar interagieren k¨onnen. Diese mehreren lau-fenden Programme nennt manThreads (engl. f¨ur ’Faden’) oder auf Betriebssystemebene Prozesse. Hierbei m¨ussen solche Threads (und auch Prozesse) nicht wirklich parallel aus-gef¨uhrt werden. Es kann auch sein, dass der Prozessor nach und nach Teile der einzelnen Threads ausf¨uhrt, so dass der Eindruck entsteht, dass sie alle gleichzeitig laufen. Nur, wenn ein Rechner mehrere Prozessoren bzw. Prozessorkerne besitzt k¨onnen Threads und Prozesse tats¨achlich parallel ausgef¨uhrt werden und das System wird unter Umst¨anden per-formanter. Dies ist aber meist nicht das vorrangige Zeil des Einsatzes von Nebenl¨aufigkeit, sondern die Gew¨ahrleistung von Reaktivit¨at.

Ein weiterer wichtiger Begriff, welchen wir hier einf¨uhren wollen, ist die Verteiltheit einer Anwendung oder auch dasverteilte System. Solche Systeme werden meist entwickelt, um Benutzer an unterschiedlichen Orten mit einer Anwendung oder einem System arbeiten lassen zu k¨onnen. Beispiele sind Chat-Anwendungen, wie WhatsApp, das Internet ganz allgemein oder auch Videokonferenzsysteme. In der heutigen Zeit sind fast alle großen Anwendungen verteile Systeme, welche gleichzeitig auf vielen Rechnern laufen und mitein-ander kommunizieren. Damit solche Anwendungen als verteiltes System arbeiten k¨onnen und reaktiv sind, ist lokal wieder der Einsatz von Nebenl¨aufigkeit notwendig und bei der Entwicklung verteilter Systeme treten dieselben Probleme auf, wie wir sie bei der Ne-benl¨aufigkeit kennen lernen werden.

Setzt man Nebenl¨aufigkeit ein, um die Performanz zu erh¨ohen, also mehrere Prozessoren oder Rechner auszunutzen spricht man von parallelen Systemen oder kurz Parallelit¨at.

Nutzt man hierbei auch mehrere Rechner in einem Netzwerk, so verwendet man auch verteilte Systeme zur Parallelisierung von Berechnungen. Ein vielleicht bekanntes Beispiel

f¨ur die Verteilung von Berechnungen zur effizienten, parallelen Ausf¨uhrung ist die Suche nach außerirdischer Intelligenz mit den SETI-Jobs. Hier kann man seinen Computer zur Verf¨ugung stellen um interstellare Signal nach Mustern abzusuchen. Die Datenmengen und durchzuf¨uhrenden Berechnungen sind hierbei enorm groß und man versucht sie parallel auf m¨oglichst vielen Rechnern auszuf¨uhren. Andere Beispiele sind spezielle Rechnercluster, mit denen Simulationen von Wetter, Ozeanen oder ¨ahnlichem durchgef¨uhrt werden.

9.1. Probleme von Nebenl¨ aufigkeit

Wir betrachten das folgende kleine Beispielprogramm, in dem zwei Threads nebenl¨aufig ausgef¨uhrt werden. wir verwenden hier noch nicht Pythons Methoden zum Erzeugen neuer Threads, sondern Pseudocode (in Pythonsyntax), welcher zwei nebenl¨aufig Berechnungen (in zwei Threads) anst¨oßt.

Beispielprogramm i = 0

{i = i + 1 | | i = i ∗ 2} # Zwei T h r e a d s , w e l c h e n e b e n l a e u f i g

# i v e r a e n d e r n

# wenn b e i d e T h r e a d s b e e n d e t s i n d ,

p r i n t( i ) # g e b e n w i r i a u s .

In der ersten Zeile wird eine Variable i mit Null initialisiert. In der zweiten Zeile werden dann zwei Threads gestartet, welche nebenl¨aufig ausgef¨uhrt werden. Der erste Thread inkrementiert den Wert der Variablen i, der zweit verdoppelt ihn. Nach Beendigung beider Threads soll in der dritten Zeile dann die Endbeledgung von i ausgegeben werden.

Die Ausgabe des Programms ist entweder 1 oder 2, je nachdem welcher Thread zuerst aus-gef¨uhrt wird. Die Entscheidung, welcher Thread zuerst ausgef¨uhrt wird, wird vomScheduler getroffen und sollte vom Programmierer als zuf¨allig angesehen werden. Der Scheduler ist Teil des Betriebssystems oder der Laufzeitumgebung von Python und wird von vielen Fak-toren, wie z.B. den anderen Threads, die in diesem Moment auf dem Computer ausgef¨uhrt werden, beeinflusst.

Da das Programm zwei m¨ogliche Ausgaben erzeugen kann, verh¨alt es sich nichtdeter-ministisch. Welches Ergebnis ausgegeben wird, h¨angt vom Scheduling der Threads ab.

Es liegt eine sogenannte Race-condition vor. Manchmal m¨ochte man als Programmierer Race-Conditions vermeiden, manchmal aber auch nicht, wie in diesem Beispiel, wo wir die beiden Berechnungen explizit nebenl¨aufig und nicht sequentiell ausf¨uhren. Ein weite-res gutes Beispiel f¨ur gewollte Race-Conditions sind Online-Shops. Hier ergeben sich die unterschiedlichen m¨oglichen Ergebnisse einer Berechnung durch das Verhalten der K¨aufer.

Der K¨aufer, wer ein Produkt zuerst bezahlt, erh¨alt das Produkt. Das Systemverhalten h¨angt hier also nicht von einem Scheduler, sondern vom Verhalten der Komponenten des verteilten Systems ab. Dies ist von den Online-H¨andlern aber gew¨unscht, da sie ja explizit vorher nicht nicht wissen, wer welchen Artikel wann kaufen wird. Vielmehr leben sie gerne mit Race-Conditions, bei denen jederzeit Kunden beliebige Artikel kaufen k¨onnen.

Betrachten wir aber nocheinmal unser Programm von oben. Es ist tats¨achlich auch denkbar, dass noch ein drittes Ergebnis herauskommen: 0. Wir haben an keiner Stelle gesagt, dass eine Berechnung i = i + 1 oder i = i ∗ 2 in einem Schritt ausgef¨uhrt werden. Es k¨onnte auch sein, dass ein Thread zwischendurch unterbrochen wird. Beide Zuweisungen bestehen aus mehreren Schritten

1. Berechnung des Ausdrucks (Stackmaschine) (Pushen der Belegung von i, Pushen der 1, Berechnen der Funktion)

2. Zuweisung des Ergebnisses zu der Variable i

Das Ergebnis 0 ergibt sich durch folgenden Programmablauf:

1. Die Zahl 0 wird in der Variable i gespeichert.

2. Der erste Thread ließt i = 0 vom Speicher und rechnet 0 + 1 = 1.

3. Der Scheduler unterbricht den ersten Thread, bevor er das neue Ergebnis in i ge-speichert hat.

4. Der zweite Thread ließt i = 0 vom Speicher und rechnet 0 ∗ 2 = 0.

5. Der Scheduler unterbricht den zweiten Thread, bevor er das neue Ergebnis in i gespeichert hat.

6. Der erste Thread speichert das Ergebnis i = 1 und ist fertig.

7. Der zweite Thread speichert das Ergebnis i = 0 und ist fertig. Hierbei ¨uberschreibt er das Ergebnis des ersten Threads.

8. Es wird das Ergebnis 0 ausgegeben.

Die Ergebnisse 1 und 2 haben wir bei der nebenl¨aufigen Ausf¨uhrung der beiden Threads erwartet. Die 0 ist wahrscheinlich aber kein erw¨unschtes Ergebnis, da die beiden Threads sich gegenseitig behindert haben und im allgemeinen bei solchen Threadwechseln innerhalb von Berechnungen v¨ollig unerwartete Ergebnisse auftreten k¨onnen.

In diesem simplen Beispiel erscheint das vielleicht nicht sinnvoll, aber dieses Beispiel l¨asst sich auch sehr gut auf den Fall einer Onlinebestellung ¨ubertragen, bei dem zeitgleich zwei Kunden den gleichen Artikel kaufen wollen, dieser aber nur noch einmal vorhanden ist. Dem Onlineh¨andler wird es wahrscheinlich egal sein, welcher der beiden Kunden das Produkt kauft (entspricht dem Ergebnis 1 oder 0). Aber es w¨are ziemlich unpraktisch, wenn entweder keiner der beiden oder beide Kunden das Produkt kaufen w¨urden (entspricht dem Ergebnis 0). Ein weitere M¨oglichkeit, welche zu dem Ergebnis 0 passen k¨onnte, w¨are die Situation, dass der eine Kunde bezahlt, aber das Paket an den anderen geschickt wird.

Wir sehen, dass man gerne gew¨ahrleisten m¨ochte, dass gewisse Bereiche nebenl¨aufiger Berechnungen nicht vom Scheduler unterbrochen werden sollen. Man m¨ochte, dass sie atomar ausgef¨uhrt werden. Aber welche Bereiche sind dies?

Betrachtet man noch einmal unser erstes Beispielprogramm, so entsteht ja das Problem, da beide Threads auf derselben Variable operieren. W¨urde der eine Thread auf einer anderen Variable arbeiten, g¨abe es keine Probleme. Die Berechnungen w¨aren dann unabh¨angig voneinander und k¨onnten beliebig verschachtelt ausgef¨uhrt werden, ohne dass sich das Gesamtergebnis ¨andert.

Der problematische Fall liegt also vor, wenn zwei oder mehr Threads auf dieselben

Res-sourcen zugreifen (bei uns oben die Variable i). Man spricht hier auch von einergeteilten Ressource. Dies k¨onnen Variablen sein, aber auch geteilte Dateien oder Datenbanken.

Die Codest¨ucke, welche nebenl¨aufig ausgef¨uhrt werden k¨onnen und geteilte Ressourcen ver¨andern k¨onnen nennt mankritischen Bereich. Bei uns also die beiden Codest¨ucke, wel-che nebenl¨aufig ausgef¨uhrt werden sollen.

Bei einem Onineshop stellt in de Regel die Datenbank eine geteilte Ressource dar. In ihr findet man, wie oft ein Artikel verf¨ugbar ist und mehrere Kunden k¨onnen Eintr¨age ne-benl¨aufig ¨andern, wenn sie z.B. Artikel kaufen oder zur¨ucksenden. Diese Operationen sind also kritische Bereiche, welche die geteilte Datenbank manipulieren. In der Regel versucht man dies feiner zu organisieren und erm¨oglicht nebenl¨aufige Datenbankver¨anderungen in unterschiedlichen Tabellen, ¨ahnlich, wie in Programmen nebenl¨aufige Ver¨anderungen un-terschiedlicher Variablen unkritisch sind. Im Onlinehandel w¨are es sicherlich unkritisch, wenn zwei Kunden gleichzeitig unterschiedliche Artikel kaufen.

9.2. Threads in Python

Bevor wir uns mit der L¨osung des Problems besch¨aftigen, schauen wir und an, wie man in Python nebenl¨aufige Threads definieren und starten kann. als erstes Beispiel starten wir zwei nebenl¨aufige Threads, welche immer wie ein a bzw. ein b in der Konsole ausgeben.

import t h r e a d i n g

d e f d o n t s t o p p r i n t i n g (s t r) : w h i l e True :

p r i n t(s t r, end= ’ ’ )

# D e f i n i t i o n d e r T h r e a d s

t 1 = t h r e a d i n g . T hr ea d ( t a r g e t=d o n t s t o p p r i n t i n g , a r g s =( ’ a ’ , ) ) t 2 = t h r e a d i n g . T hr ea d ( t a r g e t=d o n t s t o p p r i n t i n g , a r g s =( ’ b ’ , ) )

# S t a r t e n d e r T h r e a d s t 1 . s t a r t ( )

t 2 . s t a r t ( )

Wir definieren den Code, welcher nebenl¨aufig ausgef¨uhrt werden soll zun¨achst als Prozedur, welche den ¨ubergeben String in einer Endlosschleife immer wieder auf dem Bildschirm ausgibt. Dann k¨onnen wir mit dem Klasssenkonstruktor Thread neue Threads konstruieren, wobei wir als target = den Prozedurnamen ¨ubergeben und als Argumente args= ein Tupel von Parametern. ein definierter Thread wird aber nicht sofort ausgef¨uhrt. Er muss noch mittels Aufruf der Thread-Methode start gestartet werden.

F¨uhrt man das Programm aus, so werden abwechselnd einige tausend as und bs ausgegeben.

Man sieht das der Scheduler recht grob zwischen den Threads wechselt und somit nicht abwechselnd ein a und ein b ausgegeben wird.

Man beachte, dass unser Programm in Wirklichkeit drei Threads verwendet. Das

Haupt-programm ist ja ebenfalls ein Thread und dieser startet zwei weitere Threads. Es w¨are also genauso gut m¨oglich, nur einen Trhead zu starten und danach das Hauptprogramm f¨ur den zweiten Thread weiterzuverwenden ( ¨Ubung).

Um unser Beispiel von oben zu simulieren, k¨onnen wir nicht einfach in zwei Threads die Be-rechnung i = i + 1 bzw. i = i ∗ 2 ausf¨uhren. Innerhalb einer Prozedur ist es in Python ja nicht erlaubt, Variablen von außerhalb zu ver¨andern. hierzu gibt es zwei M¨ oglchkei-ten. Entweder gibt man im Rumpf der Prozedur explizit an, dass man die Variable i von außen verwenden m¨ochte global i oder man speichert den Wert der Variablen in einer Datenstruktur (z.B. einer List), welche wir mutieren k¨onnen:

import t h r e a d i n g

import t h r e a d i n g

Im Dokument Programmiertechniken (NF) (Seite 86-0)