Komisches Problem mit Threads und Syncronisation

multimolti

Erfahrenes Mitglied
Hallo!

Ich versuche grade folgendes zu realisieren:
-----------------------
Das Programm soll 2x GLEICHZEITIG (!) würfeln (also Zufallszahl zwischen 1 und 6), die Ergebnisse dann zusammenzählen und abspeichern. Das Ganze läuft einige 1000 mal ab, bis ich eine halbwegs aussagekräftige Verteilung zusammenhabe.
-----------------------
Einfach nur ein Würfelprogramm zu erstellen wäre ja kein Problem, das "gleichzeitig" ist hier jedoch der Schwierigkeitsgrad. Meine Grundidee war:
  1. Eine Funktion wartet auf ein Signal von außen, würfelt dann und speichert das Ergebnis
  2. Es werden 2 Threads erstellt, die jeweils die Funktion abarbeiten sollen
  3. Nach Erstellung der Threads wird kommt das Signal von außen
  4. Darauf warten, dass beide Threads fertig sind
  5. Ergebnisse zusammenzählen
Dafür habe ich auch ein Programm geschrieben, leider funktioniert es aber nicht. Hier mal der relevante Code: (nur das innerhalb von "if (mode == RollMode.Simultaneously)" ist interessant)
C#:
        public void Roll(int count)
        {
            int result = 0;

            for (int i = 0; i < count; i++)
            {
                if (mode == RollMode.Consecutively)
                {
                    result = rand.Next(1, 7) + rand.Next(1, 7);
                }
                if (mode == RollMode.Simultaneously)
                {
                    threaddiceresults = new List<int>();

                    resetEvent = new ManualResetEvent(false);

                    Thread t1 = new Thread(ThreadDicing);
                    Thread t2 = new Thread(ThreadDicing);

                    t1.Start();
                    t2.Start();

                    resetEvent.Set();

                    result = threaddiceresults[0] + threaddiceresults[1];

                }

                stats[result]++;
            }
        }

        private void ThreadDicing()
        {
            resetEvent.WaitOne();
            threaddiceresults.Add(rand.Next(1, 7));
        }
"stats" ist die Liste, in der die Ergebnisse gespeichert werden. Das auftretende Problem ist folgendes:
Code:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
In dieser Zeile: "result = threaddiceresults[0] + threaddiceresults[1];".
Wenn ich im Debug-Modus jedoch auf die "threaddiceresults" Variable gehe, kann ich sehen, dass diese die Elemente [0} und [1] enthält. Ich dachte, vielleicht liegt das daran, dass die Exception ausgelöst wird, danach die Threads aber noch ihre Ergebnisse in die Liste "threaddiceresults" schreiben.
Daher habe ich diese while-Schleife direkt nach "resetEvent.Set();" hinzugefügt, damit auf jeden Fall gewartet wird, bis beide Threads fertig sind:
C#:
                    while (t1.ThreadState != ThreadState.Stopped && t2.ThreadState != ThreadState.Stopped) 
                    {
                        Thread.Sleep(1);
                    }
Hat aber nichts geholfen, das Problem besteht weiterhin.

Könnt ihr mir helfen? Wäre super, Danke!!
 
Zuletzt bearbeitet:
Hallo multimolti,

eines zunächst mal vorneweg: selbst wenn du Threads verwendest, wird trotzdem nacheinander gewürfelt (HyperThreading, Multicore etc. jetzt mal außer acht gelassen). Auch von der Wahrscheinlichkeitstheorie her gibt es keinen Unterschied, ob du zwei Würfel nacheinander oder gleichzeitig wirfst und die Augensumme anschließend notierst.

Da es dich aber vielleicht trotzdem interessiert, was hier schief läuft:
  1. Eine Funktion wartet auf ein Signal von außen, würfelt dann und speichert das Ergebnis
  2. Es werden 2 Threads erstellt, die jeweils die Funktion abarbeiten sollen
  3. Nach Erstellung der Threads wird kommt das Signal von außen
  4. Darauf warten, dass beide Threads fertig sind
Wo genau geschieht das in deinem Code? Mir scheint, dass du diesen Punkt 4. in der Implementierung vergessen hast. Verwende dazu die Methode Thread.Join.

Wenn ich im Debug-Modus jedoch auf die "threaddiceresults" Variable gehe, kann ich sehen, dass diese die Elemente [0} und [1] enthält. Ich dachte, vielleicht liegt das daran, dass die Exception ausgelöst wird, danach die Threads aber noch ihre Ergebnisse in die Liste "threaddiceresults" schreiben.
Mögliche Fehlerquellen: erstens wartest du nicht auf die Beendigung der Threads (s.o.) und zweitens ist die Methode List.Add nicht Thread-sicher. Du müsstest beim Eintragen in die Liste also entweder die Threads synchronisieren oder dir etwas anderes überlegen, wie du die Werte zurückbekommst.

Grüße, Matthias
 
Okay, vielen Dank für deine Antwort!

Genau das möchte ich mal ausprobieren, ob es WIRKLICH nicht gleichzeitig passiert, ich habe einen Dual-Core mit Hyperthreading, sollte also 4 Operationen gleichzeitig ausführen können, und vielleicht klappt daher das Würfeln gleichzeitig.

Zweitens, es gibt DOCH einen Unterschied bei der Wahrscheinlichkeitstheorie, es ist nicht egal, ob man gleichzeitig mit zwei nicht identifizierbaren Würfeln verdeckt würfelt oder nacheinander (zumindest behauptet das meine Mathelehrering felsenfest, und selbst nach mehreren Anfragen von mir und einigen Klassenkameraden mit Versuchen, es zu wiederlegen, ist sie immer noch der Meinung. Bin zu faul selber 1000x zu Würfeln, also wollte ich das den PC machen lassen).
Worauf genau das beruht wollte ich beschreiben, nachdem der PC es bestätigt hat :P

3.: Naja, ich dachte das hätte ich durch die while-Schleife gelöst. Aber ich schaue mir die Join() Sache noch mal an.

4. Die Threads sind doch synchronisiert, oder? Genau das müsste diese ResetEvent-Sache doch machen!

Kannst du mir noch mal genau aufschreiben, wie du das jetzt meinst? Also in den Code einbauen wenn's geht? Wäre nett, Danke!

EDIT:
So klappt das jetzt, super, ist aber EXTREM langsam! Kann ich es beschleunigen, indem ich z.B. die Threads nicht jedes mal neu erstelle, sondern immer die gleichen neu starte?
C#:
            for (int i = 0; i < count; i++)
            {
                int result = 0;

                if (mode == RollMode.Consecutively)
                {
                    result = rand.Next(1, 7) + rand.Next(1, 7);
                }
                if (mode == RollMode.Simultaneously)
                {
                    threaddiceresults = new List<int>();

                    resetEvent = new ManualResetEvent(false);

                    Thread t1 = new Thread(ThreadDicing);
                    Thread t2 = new Thread(ThreadDicing);

                    t1.Start();
                    t2.Start();

                    resetEvent.Set();                   

                    t1.Join();
                    t2.Join();

                    result = threaddiceresults.Sum();
                }

                stats[result]++;
            }
 
Zuletzt bearbeitet:
Zweitens, es gibt DOCH einen Unterschied bei der Wahrscheinlichkeitstheorie, es ist nicht egal, ob man gleichzeitig mit zwei nicht identifizierbaren Würfeln verdeckt würfelt oder nacheinander (zumindest behauptet das meine Mathelehrering felsenfest, und selbst nach mehreren Anfragen von mir und einigen Klassenkameraden mit Versuchen, es zu wiederlegen, ist sie immer noch der Meinung. Bin zu faul selber 1000x zu Würfeln, also wollte ich das den PC machen lassen).
Da die Zufallsvariablen "Augenzahl erster Würfel" und "Augenzahl zweiter Würfel" stochastisch unabhängig sind, ist die zeitliche Abfolge der Zufallsexperimente unerheblich. Ich kenne die genaue Aussage deiner Lehrerin natürlich nicht, aber anhand der Informationen, die ich von dir habe, liegt sie falsch.

3.: Naja, ich dachte das hätte ich durch die while-Schleife gelöst. Aber ich schaue mir die Join() Sache noch mal an.
Die Hinweise zu ThreadState beachten.

4. Die Threads sind doch synchronisiert, oder? Genau das müsste diese ResetEvent-Sache doch machen!
Du synchronisierst in gewisser Weise den Start der Abarbeitung der Threads, nicht aber den Zugriff auf die Liste. Dazu müsstest du z.B. mit lock arbeiten.

So klappt das jetzt, super, ist aber EXTREM langsam! Kann ich es beschleunigen, indem ich z.B. die Threads nicht jedes mal neu erstelle, sondern immer die gleichen neu starte?
Das wird nicht klappen. Sobald ein Thread seine Ausführung beendet hat, kann man ihn nicht neu starten.

Grüße, Matthias
 
Zurück