[C++/QT] Threads

[Moepi]1

Lt. Commander
Registriert
Jan. 2002
Beiträge
1.233
So da bin ich wieder mit nem neune Problem.

Diesmal gehts um Threads. Ziel soll es sein, die Kommunikation (auf der Clientseite) zwischen Server und Client in einem eigenen Thread zu programmieren. Dazu habe ich mir eine eigene Klasse erstellt, welche die Netzwerkfunktionen (biser nur Connect, Disconnect und Send) implementiert. Das Objekt, das ich aus dieser Klasse erstelle, soll vom Connect bis zum Disconnect existieren, in einem eigenen Thread laufen und beim Disconnect wieder vernichtet werden (bzw. der zugehörige Thread soll beendet werden).
Das Threadprinzip mit QT (und auch mit der pthread Lib) ist klar. Allerdings erstelle ich derzeit praktisch nur eine Funktion, welche in einem Thread läuft - ich will aber das komplette Objekt mit all seinen Funktionen im Thread haben. Vielleicht wirds mit dem Codeausschnitt einfacher:

Code:
#ifndef MYNET_H
#define MYNET_H
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <QThread>
#include <QTcpSocket>
#include <QTNetwork>

class Mynet : public QThread
{
	Q_OBJECT
	
	public:
		void run();
		int cState;
		void connectToServer(int port, QString ip);
		void sendData(int current_commands[], int block_size);
		void disconnect();
	public slots:
		void connectionOK();
		void connectionFailed();
	signals:
		void connected();
		void notConnected();
		void connecting();
	private:
		QTcpSocket cli_sock;
};

#endif


void Mynet::run()
{
	connect(&cli_sock, SIGNAL(connected()), this, SLOT(connectionOK()));
	connect(&cli_sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionFailed()));
	cState = 0;
}

void Mynet::connectToServer(int port, QString ip)
{
	cli_sock.connectToHost(ip, port, QIODevice::ReadWrite);
	cState = -2;
	emit connecting();
}

void Mynet::connectionOK()
{
	cState = 1;
	emit connected();
}

void Mynet::connectionFailed()
{
	cState = -1;
	emit notConnected();
}

void Mynet::sendData(int current_commands[], int block_size)
{
	int sent;
	cli_sock.write((const char*)current_commands, block_size);
}

void Mynet::disconnect()
{
	cli_sock.disconnectFromHost();
	cState = 0;
}

Im Code seht Ihr oben die Headerfile, in der ich die Klasse QTHread reimplementiere. Die Funktion run() erzeugt einen neuen Thread. Allerdings macht dieser Thread wirklich nur das, was in run() weiter unten steht. Alle anderen Funktionen (insbesondere send() ) sind nicht Bestandteil dieses Threads. Zumindest haben das meine Versuche mit sinnlosen Zählschleifen ergeben - nur eine CPU wird belastet. Wenn ich die Schleife aber in die run() mache, sind beide CPUs dran.

Ich hoffe Ihr versteht was ich auszudrücken versuche. Ich weiß einfach nicht, wie ich all die Funktionen der Klasse in einen Thread packen kann (bzw. ob das überhaupt geht).
Die QT Thread Examples (vor allem der QTThreadedFortuneServer) schreiben einfach alles in die run() rein - also vom Erstellen des Sockets zum Senden der Daten bis hin zur Terminierung des Sockets - logisch, dass dann alles in nem eigenen Thread läuft. Aber klappt hier nicht...
 
Du legst neue Threads an, in dem du Objekte der KLasse MyThread erstellst:
MyThread ThreadA;
MyThread ThreadB;

Gestartet werden QThreads in dem die Methode start() eines Threads aufgerufen wird:
ThreadA.start();

Dann veranlasst diese unter anderem, dass die Methode run() ausgeführt wird.
Die run-Methode ist quasi die main() eines Threads.
Das heißt, solange die Methode run() läuft, läuft auch der Thread. Wenn run() zurückkehrt wird auch der Thread beendet.

Um beispielsweise den Thread ewig laufen zu lassen reicht einen while(1) {} Schleife ;).


Das waren die Basics in Kürze.
Du musst jetzt irgendwie dafür sorgen, dass du in der run() bleibst, z.B. durch eine while-Schleife.
Durch eine Bedingung könntest du die Schleife verlassen und den Thread somit beenden, weil ja run() zurückkehrt.

Lies dir hier mal die Detailed Description durch: http://doc.trolltech.com/4.1/qthread.html#details
Da wird beschrieben wie du die EventLoop im Thread nutzen kannst.
 
So im Prinzip war das schon klar. Wenn ich jetzt in die run() unten ein sleep(100) ranhänge, klappts auf jeden Fall nicht. Dann laufen 2 so gestartete "Threads" mit ner Zählschleife wieder als einer.
Wenn ich in die run() einfach ein for(;;) reinmach, dann darennt er sich da oben natürlich, was zu 100% Auslastung auf dem betreffenden Kern führt.

Weiß jemand, was es mit dem Befehl "exec()" in Verbindung mit QThreads auf sich hat? Die Beschreibung ist da etwas dürftig: "Enters event loop and waits until exit() is called" - hört sich gut an, nur Beispiel hab ich keins gefunden...


Haken ist einfach, dass ich nicht alle Funktionen von der run() aus aufrufen kann. Dort, wo ich den Thread (das Objekt) nutze, brauche ich Zugang zu dessen Methoden.

Das einzige Workaround, das mir einfällt, wäre folgendes:
Ich könnte einfach die send() Funtkion run() nennen. Auf diese Weise würde jedes mal, wenn er Daten senden will, ein neuer Thread erzeugt. Allerdings störts mich etwas, dass vorraussichtlich mehrmals in der Sekunde ein neuer Thread erzeugt werden soll. (Falls dieses Workaround überhaupt lauffähig wäre...)
 
Zuletzt bearbeitet:
Ich komm nun mal eher aus der Java-Ecke... aber prinzipiell sollte es dasselbe sein. Wenn die run-Methode abgearbeitet wurde, ist der Thread automatisch "tot". Du musst also höchst-persönlich dafür sorgen, dass die run-Methode nur dann zum Abschluss kommt, wenn der Thread sterben soll. Du würdest also z.B. ein while(true) {}-Block in die run integrieren, wobei du aber in jedem Schleifendurchlauf ein Päuschen von 10/100ms einlegst, um keine 100%ige CPU-Auslastung zu generieren.

Damit das Ganze dann nicht in einer Dauerschleife endet, definierst du innerhalb der while-Blocks eine Abbruchbedingung, welche sich auf das Interrupted-Flag des Threads bezieht. Thread können prinzipiell ja nicht einfach gestoppt werden, weil nicht bekannt ist, wo der Thread gerade wie ausgeführt wird. Das würde zu Inkonsistenzen führen. Ergo kann man einen Thread nur auffordern, seine Arbeit einzustellen und das nennt sich dann Interrupt. Du prüfst also in jedem Schleifendurchlauf auch noch, ob der Thread interrupted wurde. Wenn ja, springst du per break aus der Schleife raus und damit stirbt der Thread implizit.
 
Dasselbe macht glaub ich "exec()" in QT. Das Problem ist ein anderes:

Wenn ich inerhalb der oben beschrieben Klasse eine Funktion "loop()" einbaue, die mir einfach die Prozessoren zum testen auslasten soll und ich diese Funktion von der run() aus aufrufe, dann klappts ja bestens.
Wenn ich allerdings (und das ist es, was ich will und muss) die loop() von dort aufrufe, wo ich den Thread gestartet habe (also irgendwie über mythread.loop() ), dann läuft mir die Schleife nicht als eigener Thread sondern dort, von wo ich das Ding aufgerufen hab.
Und das ist mein Problem. Klar könnte ich jetzt in der run() hergehen und dort ne while Schleife machen und ständig prüfen (also alle 100ms), ob neue Daten zum versenden bereitstehen. Aber ist das nicht etwas unelegant? Abgesehen davon würde es so leicht nicht gehen, da dem Thread die Daten übergeben werden müssen (liegen ja in ner anderen Klasse). Der Thread weiß ja nicht wo wann was und wo...


Ne tolle Möglichkeit wäre:
Code:
void Mynet::run()
{
	connect(&cli_sock, SIGNAL(connected()), this, SLOT(connectionOK()));
	connect(&cli_sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionFailed()));
	connect(&cli_sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(loop()));
[B]	connect([COLOR="Red"]vonWoDuErstelltBist[/COLOR], SIGNAL(newData()), this, SLOT(send()));[/B]
	int cState = 0;
	exec();
}

Und da ist aber das Problem, dass ich keine Signale in den Thread reinbringe, da der Thread ja nicht weiß, wo er mal erstellt werden wird...

/edit:
Code:
connect([COLOR="Red"]this->parent()[/COLOR], SIGNAL(newData()), this, SLOT(send()));
Klappt net - warum?
 
Zuletzt bearbeitet:
das signal müsste selbstverständlich von benutzern deines objectes connected werden, und nicht vom ibject selber...

du solltest dir mal ein paar dinge über threads durchlesen. vorallem über thread syncronisation und event loops.

kurz: threads sind eigenständig. wenn du willst das ein thread einem anderen sagt was er tun soll, musst du das extra tun. das geht nicht einfach so per funktionsaufruf. die einzige möglichkeit IST die (while) schleife. sowas nennt man dann event-loop. das ist ganz normal, und die einzige möglichkeit mehr als ein star vor sich hin laufendes programm zu schreiben. glücklicherweise bringt qt gleich eine mit, du brauchst also nicht selbst eine schleife und die dazugehörige event-bearbeitung programmieren. wenn du mit signals und slots arbeiten willst ist übrigens ebenfalls eine (qt) eventloop erforderlich, da ansonsten der slot im sendenden thread ausgeführt wird(eben als normaler functionsaufruf).

wenn du direkt programmieren willst(sprich einfach functionen aufrufen) dann sind threads einfach nichts für dein problem(oder deine herrangehensweise...). der punkt von threads ist eben das sie nicht teil eines anderen threads, sondern eben eigenständig sind. mit allen vor und nachteilen.
 
Zuletzt bearbeitet:
Danke, das Dokument von QT hatte ich bereits gelesen. Wahrscheinlich geh ich einfach nur wieder falsch an die Sache ran...
 
wenn du threads benutzen willst sollten die so gut wie möglich autonom arbeiten. das einzige was du zum kommunizieren zwischen threads benutzen kannst ist der gemeinsam genutzte speicher. threads kosten dir also nur rechenzeit(beim nachgucken ob der eine thread den endlich nun so weit ist wie der anderen ihn den gerne hätte) wenn sie zu stark voneinander abhängig sind. für solche fälle benutzt man dann besser non-blocking klassen in einem thread - qt bietet dir da sehr viel möglichkeiten mit einer sehr schönen api(da es ursprünglich ganz auf threads verzichtet hat).

du solltest also entweder die thread klasse ausbauen, damit sie mehr alein tun kann oder für dieses problem eben auf threads verzichten.

interessant für dich wäre vielleicht auch diese klasse. so könnte dein thread relativ komfortabel darauf warten das daten zum versenden da sind. intern wird die klase zwar auch nicht viel anderes machen als ständig zu gucken ob eben neue daten da sind, aber wenigstens kümmert sich qt darum das dabei nicht soviel cpu leistung verbraucht wird.

ansonsten würde ich aber immer erstmal sehen ob man für solche dinge nicht mit non-blocking klassen besser klar kommt. zwar neigen die bei großen datenmängen immernoch eher zum blockieren der ui als ein eigener thread, aber zum versenden von daten sind die meistens doch besser geeignet. ein extra thread eignet sich dafür eher zum enpfangen von daten(schließlich läuft im gui thread eh schon eine event loop).
 
Danke Dir. Ich werd mich da morgen nochmal dran setzen...
 
Zurück
Oben