Disclaimer: Dieser Thread wurde aus dem alten Forum importiert. Daher werden eventuell nicht alle Formatierungen richtig angezeigt. Der ursprüngliche Thread beginnt im zweiten Post dieses Threads.
CyclicBarrier - Verstaendnisproblem
Hallo,
ich bearbeite die Teilaufgabe mit dem Gauss-Filter und bekomme die Synchronisation nicht hin.
Angenommen man hat eine Variable [m]double[][] array[/m], die man zwei Threads vollstaendig uebergibt. Ausserdem hat man in der gaussFilter()-Methode eine CyclicBarrier(2) erstellt, die man ebenfalls den Threads uebergibt.
Wenn nun Thread1 den Eintrag [0][1] nicht bearbeitet, aber Thread2 hingegen schon und in [0][1] den Wert 5 reinschreibt, dann sieht Thread1 in array[0][1] den Wert 0 und Thread2 sieht in array[0][1] den Wert 5. Wenn nun beide Threads barrier.await() aufrufen, dann steht in array[0][1] bei Thread1 immer noch 0 und in Thread2 immer noch 5. Wieso wurde das nicht synchronisiert? Was koennte ich falsch gemacht haben?
Edit: Ich habe den gaussianVisualTest mal mit nur einem Thread ausprobiert und gemerkt, dass am Ende der run() Methode der richtige Wert in [0][1] steht. Nach join in der gaussianFilter-Methode steht jedoch wieder ein alter Wert drin. Wie kann denn sowas passieren? Vielleicht durch das Tauschen, dass ich aus der sequientiellen Implementierung uebernommen habe?
Wenn nach [m]join()[/m] oder [m]await()[/m] bei dir immer noch 0 in dem beschriebenen Eintrag steht, dann hast du vermutlich unterschiedliche Arrays übergeben. Bist du dir sicher, dass du in das gleiche Array schreibst, welches du nachher ausliest?
Was bei solchen Problemen oft hilft, ist, sich ein Minimalbeispiel zu schreiben, in dem der Fehler auftritt. Meistens entdeckt man dann den Fehler deutlich schneller, da man so die Fehlerursache gut eingrenzen kann.
Das habe ich jetzt auch mal gemacht und bin mir nun sicher, dass es am tauschen der zwei FractalResults scheitert. Ich versuche es mal an einem einfachen Beispiel zu zeigen, vllt kann mir dann jemand sagen, was ich anders machen muss.
public class Test {
public void methode() {
double[][] array1 = new double[2][2]; // alles mit 5 initialisiert
double[][] array2 = new double[2][2]; // alles mit 10 initialisiert
CyclicBarrier barrier = new CyclicBarrier(2);
(new Inner(array1, array2, barrier, 0)).start();
Thread last = new Inner(array1, array2, barrier, 1);
last.start();
last.join();
}
private class Inner extends Thread {
// ... lege hier Variablen an
public Inner(...) {
this.array1 = array1;
this.array2 = array2;
this.barrier = barrier;
this.id = id;
}
public void run() {
barrier.await();
if (id == 0) {
// tausche arrays
double[][] tmp = array1;
array1 = array2;
array2 = tmp;
}
barrier.await;
// jetzt sieht Thread1 die getauschten arrays, Thread2 aber nicht...
// d.h. bei Thread1 hat array1 die Werte 10 und bei Thread2 hat array1 noch die Werte 5
}
}
}
Meine Frage lautet nun, wie ich es schaffe, dass beide Threads in Zeile 32 die getauschten Arrays sehen.
Das wird daran liegen, dass array1 und array2 (wahrscheinlich – die Deklaration hast du ja ausgelassen) Instanzfelder sind. Das bedeutet, jeder Thread hat sein eigenes Feld array1 und sein eigenes Feld array2. Das wiederum bedeutet, dass jeder Thread selbstständig die Arrays tauschen muss. Du könntest zwar alternativ array1 und array2 als Klassenfelder (static) deklarieren, das würde aber verhindern, dass der Filter gleichzeitig von unterschiedlichen Threads aufgerufen werden kann (→ Wettlaufsituation). Zudem müsstest du nach der Verwendung diese Felder auf [m]null[/m] setzen, damit der Garbage Collector die Arrays aufräumen kann.
Ah ok, danke. Ich hatte mich an die Vorlesungsfolien gehalten, aber natuerlich uebersehen, dass dort die Variablen static waren.
Ich machs lieber ohne static und habe jetzt jeden Thread dstColors und colors tauschen lassen. Ein Problem habe ich noch:
Es stehen am Ende der run()-Methode der inneren Klasse die richtigen Werte in dstColors. Die gaussFilter-Methode selbst sieht aber iwie in colors die Werte von dstColors und in dstColors die Werte von colors. Denn wenn ich in gaussFilter() colors returne und nicht dstColors, dann laufen die Testfaelle durch. Ist das so auch in Ordnung?
Das wird das gleiche Problem wie vorher sein: Du tauschst in den Threads die FractalResults, aber nicht in der Methode, die die Threads erzeugt. Wenn du also colors und dstColors in den Threads tauschst, dann in dstColors schreibst, hast du aus Sicht der äußeren Methode in colors geschrieben.
Ich verstehe das ganze immer noch nicht so richtig…
Wenn ich in der aeusseren Methode ein Array anlege und den Threads uebergebe, welche dann Werte in diesem Array speichern, dann sieht das nach der Synchronisation die auessere Methode. Wieso versteht sie dann nicht, wenn ich den FractalResults color und dstColor neue Werte zuweise, indem color die Werte von dstColor und dstColor die Werte von color bekommt?
Ich moechte wirklich gerne wissen, wieso das so ist. Aber abgesehen davon interessiert es mich noch, ob es Punktabzug gibt, wenn ich wie oben beschrieben colors returne, in denen aus der Sich der aeusseren Methode die richtigen Werte stehen?
Ich versuche das mal anhand einer Visualisierung des Speichers zu verdeutlichen. Angenommen, wir befinden uns zu Beginn in der Methode [m]foo()[/m] und deklarieren zwei Arrays [m]a1[/m] und [m]a2[/m]. Dann zeigen [m]a1[/m] und [m]a2[/m] auf [m]null[/m].
foo() {
a1 -> null
a2 -> null
}
Jetzt erzeugen wir je ein Array und weisen diese den beiden Variablen zu:
foo() {
a1 -> A1
a2 -> A2
}
Nun erzeugen wir zwei Threads und übergeben diesen die Arrays.
foo() {
a1 -> A1
a2 -> A2
}
Thread0 {
a1 -> A1
a2 -> A2
}
Thread1 {
a1 -> A1
a2 -> A2
}
Jetzt vertauscht der erste Thread die Werte, die in [m]a1[/m] und [m]a2[/m] stehen:
foo() {
a1 -> A1
a2 -> A2
}
Thread0 {
a1 -> A2
a2 -> A1
}
Thread1 {
a1 -> A1
a2 -> A2
}
Beachte, dass nur [m]a1[/m] und [m]a2[/m] des ersten Threads modifiziert wurden, da dieses eigenständige Referenzen sind und nichts mit den [m]a1[/m]s und [m]a2[/m]s des zweiten Threads oder gar der Methode [m]foo()[/m] zu tun haben. Jetzt könnte auch der zweite Thread seine Arrays vertauschen:
foo() {
a1 -> A1
a2 -> A2
}
Thread0 {
a1 -> A2
a2 -> A1
}
Thread1 {
a1 -> A2
a2 -> A1
}
Auch das ändert nichts an den Referenzen in [m]foo()[/m]. Wenn jetzt der zweite Thread einen Wert in [m]a2[/m] schreibt (dies entspricht Objekt [m]A1[/m]), sieht die Methode [m]foo()[/m] diese Änderung in [m]a1[/m], da [m]a1[/m] auf [m]A1[/m] zeigt (das ist das veränderte Objekt). Bei nicht-primitiven Typen kommt es also immer darauf an, auf welches Objekt die entsprechende Variable zeigt.
Danke mkmdl fuer die ausfuehrliche Anwort!
Jetzt hab ichs verstanden.