Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Als Anfänger in der Threadprogrammierung benötige mal wieder einige grundlegende Denkanstöße. Ich arbeite gerade wieder an meinem Morseprogramm, bei dem ich mich auch erstmalig in Tread-Programmierung versucht habe. Damit wollte ich das Einlesen der Zeichen von der Tonerzeugung entkoppeln, was auch gut funktioniert. Für die Verbindung der Threads habe ich ein FIFO eingerichtet und genau da liegt mein Problem. Ein FIFO hat die unangenehme Eigenschaft auch mal voll zu sein, sodass der Eingabethread warten muss. Bisher lief das Programm dort in einer Schleife, bis der FIFO Eintrag erfolgreich war. Da das Abarbeiten eines Zeichenelements im Mittel etwa 50 ms dauert erscheint es mir unsinnig diese Zeit in einer Warteschleife zu verbringen. Ein testweises Ausgeben des wartenden Zeichens schreibt jedenfalls so viele Zeichen in den Konsolenpuffer, dass das vorherige Zeichen sich nicht mehr darin befindet. In meiner Verzweifelung habe ich dann einfach mal ein usleep( 100000 );
eingebaut. Ohne usleep() liegt die CPU Auslastung bei 100% und mit bei 30% (laut htop). Beides erscheint mir nicht optimal. Wenn ich die Schlafdauer auf 1s erhöhe, sinkt die CPU Last auf ca. 4%, was knapp über der Ruhelast liegt. Allerdings ist diese Wartezeit für hohe Geschwindigkeiten zu lang, da das FIFO dann schonmal leer laufen kann (Underrun). Und so ganz nebenbei hat Google mir verraten, dass man usleep() nicht mehr benutzen soll... Gibt es dafür eine wirklich elegante Lösung?
|
diesch
Anmeldungsdatum: 18. Februar 2009
Beiträge: 5072
Wohnort: Brandenburg an der Havel
|
Mit select() solltest du ohne Schleife warten können, bis du schreiben kannst.
|
punischdude
Anmeldungsdatum: 14. Oktober 2006
Beiträge: 1596
Wohnort: Unterfranken
|
Unter Umständen ließe es sich auch mit Semaphoren 🇬🇧 lösen.
|
Darkstar999
Anmeldungsdatum: 21. September 2008
Beiträge: 324
|
Hallo probiere es doch einfach mit allen drei zu Verfügung stehenden Funktionen aus mir fehlt dafür leider gerade die Zeit! | #include<unistd.h>
sleep();
usleep();
|
Oder | #include<time.h>
nanosleep();
|
mfg darkstar999
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Select wurde in meinem schlauen Galileo Buch auch schon erwähnt, aber ich bin nicht so richtig dahintergekommen wie das funktioniert. Jedenfalls habe ich keinen File- oder Socket- Descriptor. Leider ist das Programm zu lang um es hier komplett zu zeigen (>45kb) und ausserdem ist dort noch eine Menge unbearbeiteter Beispielcode aus den Alsa Beispielen drinne. Aber vielleicht hilft es, mal die Eingabe in das FIFO zu zeigen: ...
#define ActionBufSize 40 /* Anzahl der mögl. Einträge im Ringpuffer */
typedef struct {
short act; /* Aktionscode bzw. Zielzustand 0, 1, ansteigend, abfallend */
short step; /* Schrittweite, Amplitudenaenderung etc. */
int value; /* Zielwert, Zeichen ... */
double freq; /* Zeichenfrequenz */
long counter; /* Samplecounter, Tondauer */
} ActionBufRec;
...
/* -------------------------------------------------------------------
**
** Write one record to the queue and returns 1 on success
*/
int
queue_in( ActionBufRec * input )
{
ActionBufRec * qp = action_buffer + q_input_index;
pthread_mutex_lock( &queue_mutex );
if ( q_free > 1 ) {
qp->act = input->act;
qp->step = input->step;
qp->value = input->value;
qp->freq = input->freq;
qp->counter = input->counter;
q_free--;
q_count++;
q_input_index++;
if ( q_input_index >= ActionBufSize ) {
q_input_index = 0;
}
pthread_mutex_unlock( &queue_mutex );
return 1;
}
pthread_mutex_unlock( &queue_mutex );
return 0;
}
Wenn dieser Programmteil signalisiert dass 0 Elemente in das FIFO eingegeben werden konnten, hängt der "Auftraggeber" in der Warteschleife. Das eigentliche Kriterium ist also die Variable "q_free" und kein Filedescriptor. Ich komme jetzt aber nicht drauf, wie ich diese Erkentniss verwerten kann. [edit]: Hab wieder mal zu lange editiert. Semaphoren muss ich mir erst noch ansehen.
|
posti
Anmeldungsdatum: 30. März 2009
Beiträge: 2086
|
Hi Gibt es in C kein Äquivalent zu 'NOP' in Assembler? (No OPerate - braucht mehrere Takte, bei Schleifen interessant) Oder Interrupt per Timer und Programm schlafen legen? (per Interrupt nach ?? ms aufwecken) MfG
|
Darkstar999
Anmeldungsdatum: 21. September 2008
Beiträge: 324
|
posti schrieb:
Oder Interrupt per Timor und Programm schlafen legen? (per Interrupt nach ?? ms aufwecken)
Also das äquivalent wären ja die verschiedenen sleep Funktionen Interrupts gehen ja nur bei µCs oder liege ich da falsch ?! mfg darkstar999
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Also NOP ist in etwa so ineffektiv wie eiene Warteschleife im Stile
while( not_ready() ){
/*do_nothing*/;
}
Es geht ja eigentlich darum, die nicht benötigte Zeit anderen Prozessen zu Verfügung zu stellen, damit das System nicht unnötigt belastet wird. Nach meinen bisherigen Erkenntnissen verbringen einige *sleep* Versionen bei zu kurz eingestellter Zeit diese auch nur in Warteschleifen ohne die nicht benötigten Resourcen anderen Prozessen zu Verfügung zu stellen. Das ist irgendwie unbefriedigend. Für die Fehlermeldungen meines "headless Servers" ist das natürlich nicht so wichtig, da Fehlermeldungen ja die Ausnahme sein sollen, aber ich möchte natürlich auch lernen, wie man sowas richtig macht. Aber für heute stelle ich die Denkarbeit erstmal ein und lege mich hin..
|
Darkstar999
Anmeldungsdatum: 21. September 2008
Beiträge: 324
|
Dakuan schrieb: Es geht ja eigentlich darum, die nicht benötigte Zeit anderen Prozessen zu Verfügung zu stellen, damit das System nicht unnötigt belastet wird. Nach meinen bisherigen Erkenntnissen verbringen einige *sleep* Versionen bei zu kurz eingestellter Zeit diese auch nur in Warteschleifen ohne die nicht benötigten Resourcen anderen Prozessen zu Verfügung zu stellen. Das ist irgendwie unbefriedigend.
Deswegen probiere mal nanosleep = nanosec.
usleep = microsec.
sleep = millisec. mfg darkstar999
|
TraumFlug
Anmeldungsdatum: 16. Juli 2009
Beiträge: 999
Wohnort: zwischen den ohren
|
Polling mit sleep-funktionen ist denke ich mal ehe suboptimal. Dafür gibt's in der pthreads-api bessere funktionen zur synchronisation von threads - mutexes, conditionals usw. ein semaphore wäre bei dir vielleicht overkill, es geht ja nur darum, dass ein thread wartet bis er wieder gebraucht wird, während der andere konstant abarbeitet. ich glaube das ginge so: thread 1 (stopft die pfeife): wenn die pfeife voll ist, "pthread_cond_wait()", bis thread 2 (raucht die pfeife) der Meinung ist, da geht wieder was 'rein - durch "pthread_cond_signal()". musst du mal googlen, pthreads (ich nehme mal an, das benutzt du), mutex(es), conditionals. vielleicht sind conditionals auch overkill...ich müsste das morgen mal testen, aber vielleicht geht's auch wenn thread 1 2x mutex abschliesst, einmal bei'm start, und einmal wenn er "fertiggestopft" hat (dann wartet er auf den mutex, den er selbst abgeschlossen hat!), und thread 2 macht ein "unlock" wenn neue Daten reinkönnen (dann sollte thread 1 wieder aufwachen, die pfeife wieder vollstopfen und sich selbst mit "lock" schlafen schicken, bis nr. 2 wieder "weckt"). ein thread, der mit "lock" ein mutex schliessen will, das bereits geschlossen ist, wird vom kernel "schlafengeschickt" bis ein anderer thread den mutex wieder aufschliesst. also kein bedarf mehr an ungenauem sleep und polling.
|
TraumFlug
Anmeldungsdatum: 16. Juli 2009
Beiträge: 999
Wohnort: zwischen den ohren
|
Ach vergiss das oben, meine Pfeife war zu voll. Semaphores sollten das ganze gut lösen können (und am einfachsten). Manuell mit nicht-rekursivem mutex doppellock und statusvariable (die im zweifel auch noch gelock't werden müsste) wäre ein böser hack. und würde auch nur ein semaphore simulieren. sorry, die komplexität des wikipedia artikels hatte mich geblendet... 😉
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Hallo TraumFlug. Ich habe jett den ganzen Nachmittag über deine Beiträge nachgedacht. Zuerst erschien mir die Variante mit Semaphoren und sem_wait() am optimalsten. Dumm ist nur, das diese Funktion selber wieder die Semaphore verändert, was nicht in meine bisherige Programstruktur passt. Ich müsste dann den pthread_mutex_lock() rausnehmen und alles mit Semaphoren machen und das an anderer Stelle.
Ach vergiss das oben,...
Warum? pthread_cond_wait() scheint eine brauchbare Lösung zu sein, wenn ich das direkt in die oben zitierte queue_in() Funktion einbaue und dort warte anstatt mit einem Null-Ergebnis zurückzukommen. Die Freigabe kann dann sogar mit einer gewissen Verzögerung geschehen, z.B. wenn der Puffer wieder halb leer ist. Vielleicht bringt das an anderer Stelle Vorteile, denn ich lese ja von stdin ein ASCII Zeichen und weiss zu diesem Zeitpunkt noch gar nicht wie viele Elemente (Punkt, Strich, Pause) anschliessend in den Puffer müssen. Ich werde das mal ausprobieren und dann auch mal testen was htop zur CPU Auslastung sagt.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich denke der Fall ist gelöst. Ich mache das jetzt so, dass direkt beim Eintragen in den Puffer mit pthread_cond_wait() gewartet wird, bis wieder einige Elemente eingetragen werden können. Die Freigabe geschieht beim Auslesen aus dem Puffer mit pthread_cond_signal() wenn es wieder mehr als 15 freie Plätze gibt. Ich habe jetzt gerade die unnötig gewordenen Abfragen ob das Schreiben in den Puffer erfolgreich war, hinausgeworfen, was den Quellcode um 2kB verkürzt hat. Laut htop liegt die CPU Auslastung dabei bei etwa 6 oder 7 %. Ich denke das ist ein brauchbares Ergebnis. Ich danke allen, die sich mit meinem Problem beschäftigt haben.
|