Thread-Nutzung korrekt?



  • Hallo,

    ich bin noch immer an meinem erstem kleinem Konsolenspiel jetzt bin ich leider in die Lage gekommen, dass ich zwei Funktionen quasi Parallel zueinander laufen lassen möchte/muss.

    Nachdem ich nun schon viel gesucht habe (und leider nur Beispiele fand die bei mir nicht funktionier (wahrscheinlich weil sie über 10 Jahre alt sind)) habe ich mir nun mal ein Mini-Programm geschrieben, das auch schon funktioniert.

    Jetzt wollte ich aber gerne sicher gehen, dass ich das ganze auch richtig mache oder ob ich etwas falsch mache (wenn ja was?)...
    Ich bin mir da sehr unsicher nachdem ich ganz oft gelesen habe, dass threads sehr kompliziert sein sollen.

    Hier einmal mein kleines Test-Programm:

    void threadFunction() {
    
    	Sleep(3000);
    
    	for (int i = 0; i < 10; i++) {
    		std::cout << "Funktion 1 zählt: " << i << std::endl;
    		Sleep(1000);
    	}
    
    	std::cout << "Beende Funktion 1" << std::endl;
    
    }
    
    void threadFunction2() {
    
    	for (int i = 0; i < 10; i++) {
    		std::cout << "Funktion 2 zählt: " << i << std::endl;
    		Sleep(1000);
    	}
    
    	std::cout << "Beende Funktion 2" << std::endl;
    }
    
    int main() {
    
    	std::thread t1(threadFunction);
    	std::thread t2(threadFunction2);
    
    	std::string test;
    	std::cin >> test;
    
    	return 0;
    
    }
    


  • Threads sind nur dann kompliziert, wenn mehrere Threads die gleichen Ressourcen verwenden, also versucht man dies zu minimieren.

    In deinem Fall benutzen beide Threads ungeschützt std::cout, da sollte ein Mutex her. Andernfalls besteht die Gefahr, dass der eine Thread eine Ausgabe anfängt, während der andere Thread noch auf die Konsole schreibt und das gibt Salat.



  • Bei schneller Eingabe beendet sich auch dein Programm, bevor die Threads fertig sind. Ein join am Ende von main schadet sicher nicht.



  • Danke, dass habe ich leider beim testen gemerkt, dass die sich gegenseitig behindern können.

    Ein anderes Problem dass ich nun noch habe:

    Ich erstelle nun in meinem Spiel zwei Threads. Das funktioniert auch super.
    Problematisch wird es aber, wenn die die Spiel-Runde endet und eine neue beginnt. Die alten Threads bleiben quasi als "leichen" zurück und es werden wieder zwei neue erstellt. Auf Dauer kommt da einiges zusammen.
    Aber ich bekomme es auch nicht hin sie zu beenden. Ich habe es mit tx.~thread versucht und mit std::terminate die threads werden aber damit auch nicht beendet.

    Wie muss ich denn vorgehen um einen Thread zu beenden wenn ich weiß, ich brauch ihn nicht mehr?


  • Mod

    Es muss doch irgendeine logische Bedingung geben, bei der die Threads beendet werden sollen. Die Threads müssen selbstständig diese Bedingung prüfen und ggf. dann selber zum Ende kommen. Du beendest die Threads nicht von außen.



  • Ich hatte ja zuerst angenommen, dass der Thread sich selbst beendet sobald er seine Funktion abgearbeitet hat
    Diese Funktionen haben beide eine while-Schleife. Die wird verlassen sobald die laufende Runde beendet wurde und damit endet dann auch die Funktion. Das passiert auch so wie es soll. Aber der Thread bleibt trotzdem vorhanden, er tut dann nur nichts mehr.



  • Ich würde vorschlagen, dass du mal deinen Code postest mit dem du Threads erzeugst. Auf anhieb würde ich vermuten, dass deine Threads sich schon beenden, du aber immer neue Thread Instanzen erstellst.


  • Mod

    Mein Schuss ins Blaue: Fehlendes join.



  • Das hier wäre der Teil der für die Funktionsaufrufe zuständig ist.

    while(true) {
    
    	RoundFunctions RoundFunctions;
    
    	if(roundStart) {
    
    		RoundFunctions.startNewRound(userSettings["Level"], userSettings["Difficulty"]);
    
    		std::thread t1(&RoundFunctions::doDayNightCycle, &RoundFunctions, std::ref(config), std::ref(userSettings));
    		t1.detach();
    		std::thread t2(&RoundFunctions::doSpecialEvents, &RoundFunctions, std::ref(config), std::ref(userSettings));
    		t2.detach();
    
    		roundStart = false;
    
    	}
    
    }
    

    Bei jedem Start bekomme ich dann zwei zusätzliche Threads erstellt.

    Mein Schuss ins Blaue: Fehlendes join.

    Hab ich tatsäcjlich nicht. Liegt aber in dem Fall daran, dass dann das ganze nicht funktioniert wie ich will - beide Funktionen werden dann wieder nur "nacheinander" abgelaufen...


  • Mod

    Und jetzt noch bitte die aufgerufenen Funktionen und eine Erklärung, wie du feststellst, dass die Threads überhaupt noch leben. Oder anders gesagt: Wieso lieferst du nicht endlich mal ein vollständiges Minimalbeispiel?



  • und eine Erklärung, wie du feststellst, dass die Threads überhaupt noch leben.
    

    Ich lasse es ja über Visual-Studis im Debug-Modus laufen. Wenn ich die Ausführung beende werden mir auch alle Threads angezeigt die beendet wurden. In der zwanzigsten Runde ist die Liste extrem lang - also nehme ich an, dass diese Threads alle noch vorhanden gewesen sind.

    Wieso lieferst du nicht endlich mal ein vollständiges Minimalbeispiel?
    

    Weil man mich nur nach dem Teil gefragt hat in dem ich die Threads erstelle und ich nicht dachte, dass es relevant ist was in den Funktionen steht.

    Hab es nun so gelöst, dass ich die Threads einmalig vor der While-Schleife erstelle. Den Rest (ob und wann die Funktion ausgeführt wird) regel ich nun innerhalb der Funktion selber, anhand des Rückgabewertes aus der Rundenfunktion.

    Da es mich aber trotzdem interessieren würde, wo da das Problem liegen könnte hab ich es nochmal in meinem Testprogramm ausprobiert wo dasselbe Problem auftritt. Den ganzen Code poste ich hier mal vielleicht finden wir die Ursache ja noch:

    #define WIN32_LEAN_AND_MEAN
    #include <iostream>
    #include <string>
    #include <Windows.h>
    #include <map>
    #include <thread>
    
    class TestFunctions{
    public:
    	TestFunctions();
    	void TestFunctions::function1(std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap1, std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap2);
    	void TestFunctions::function2(std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap1, std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap2);
    	~TestFunctions();
    
    private:
    
    };
    
    TestFunctions::TestFunctions() {
    }
    
    void TestFunctions::function1(std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap1, std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap2) {
    
    	SYSTEMTIME Time;
    	GetSystemTime(&Time);
    	long currentTime = (Time.wSecond * 1000) + Time.wMilliseconds;
    	long endTime = currentTime + testMap1["Global"]["Config"]["Duration"];
    
    	while (currentTime < endTime) {
    		// Ich tue hier wirklich gar nichts! Nur eine Dummyschleife
    		GetSystemTime(&Time);
    		currentTime = (Time.wSecond * 1000) + Time.wMilliseconds;
    	}
    
    	return;
    
    }
    
    void TestFunctions::function2(std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap1, std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap2) {
    
    	SYSTEMTIME Time;
    	GetSystemTime(&Time);
    	long currentTime = (Time.wSecond * 1000) + Time.wMilliseconds;
    	long endTime = currentTime + testMap1["Global"]["Config"]["Duration"];
    
    	while (currentTime < endTime) {
    		// Ich tue hier wirklich gar nichts! Nur eine Dummyschleife
    		GetSystemTime(&Time);
    		currentTime = (Time.wSecond * 1000) + Time.wMilliseconds;
    	}
    
    	return;
    
    }
    
    TestFunctions::~TestFunctions() {
    }
    
    int main() {
    
    	std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap1;
    	std::map<std::string, std::map<std::string, std::map<std::string, unsigned int>>> testMap2;
    
    	testMap1["Global"]["Config"]["Duration"] = 500;
    	testMap2["Global"]["Config"]["Duration"] = 500;
    
    	bool start = true;
    	TestFunctions TestFunctions;
    
    	int i = 0; // Anzahl der Threads die über die ganze Laufzeit erstellt werden als Vergleichswert (wenn beendete Threads bei Programmende = i dann wurde nie ein Thread beendet?)
    
    	while(true) {
    		std::thread t1(&TestFunctions::function1, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    		//t1.join();
    		t1.detach();
    		std::thread t2(&TestFunctions::function2, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    		//t2.join();
    		t2.detach();
    
    		i = i + 2;
    
    		std::cout << i << std::endl;
    
    	}
    
    	return 0;
    }
    

    So läuft es auch in meinem Spiel /einziger Unterschied, die while-Schleifen in den Funktionen haben tatsächlich eine echte Aufgabe, der Effekt ist aber derselbe wie hier).



  • while(true) {
    		std::thread t1(&TestFunctions::function1, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    		//t1.join();
    		t1.detach();
    		std::thread t2(&TestFunctions::function2, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    		//t2.join();
    		t2.detach();
    
    		i = i + 2;
    
    		std::cout << i << std::endl;
    
    	}
    
    	return 0;
    }
    

    Ich kenn mich zwar nicht wirklich mit Threads aus, aber sollte das join nicht am Ende kommen, um auf das Ende der beiden Threads zu warten?

    while(true) {
    		std::thread t1(&TestFunctions::function1, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    
    		t1.detach();
    		std::thread t2(&TestFunctions::function2, &TestFunctions, std::ref(testMap1), std::ref(testMap2));
    
    		t2.detach();
    
    		i = i + 2;
    
    		std::cout << i << std::endl;
    
            t1.join();
            t2.join();
    	}
    
    	return 0;
    }
    


  • detach und danach join ist Blödsinn, entweder oder!

    https://stackoverflow.com/questions/37015775/what-is-different-between-join-and-detach-for-multi-threading-in-c

    Also: erst die threads alle erzeugen, dann alle joinen.

    Den Code von Gedankenausbruch verstehe ich nicht. Was soll mit der while(true)-Schleife erreicht werden? Den Rechner mit threads vollspammen?



  • Den Code von Gedankenausbruch verstehe ich nicht. Was soll mit der while(true)-Schleife erreicht werden? Den Rechner mit threads vollspammen?
    

    Ja, genau das war der Sinn der Sache.
    Die Funktionen die aufgerufen werden laufen exakt 500ms. Danach sollte der Thread beendet werden. Fragen waren: Warum werden sie nicht beendet, wie sorge ich dafür, dass sie beendet werden, wenn ihre Funktion ihr Ende erreicht hat?

    Schrieb ich aber alles schon.



  • Also generell würde ich dir empfehlen kurz die Wörter "Threadpool, Worker, Listener, Dispatcher" in deine persönliche Lieblingssuchmaschine einzutippen, dort wirst du bestimmt etwas finden.

    Meine Interpretation:

    Deine Threads beenden sich vermutlich, aber es werden schneller welche erzeugt, wie Sie sich beenden können. Die joins sollten am Ende kommen und soweit ich verstanden habe, brauchst du (in diesem Fall) kein detach.

    Auch möglich:
    Bei jedem Erstellen eines Threads wird ein globaler counter um eins inkrementiert. Bei jedem Beenden eines Threads wird dieser counter dekrementiert.

    Somit kannst du eine Maximalzahl an Threads einstellen, wenn nur neue Threads erzeugt werden, falls nicht schon genügen da sind.

    Diese Variable ist gemeinsam genutzt und braucht irgendeine Art Mutex oder gutes Design (bin mir nicht mehr sicher, aber präfix inkrement und dekrement sollten atomare operationen sein, und werden dadurch nicht unterbrochen, also ++threadAnzahl und --threadAnzahl verwenden)


Anmelden zum Antworten