Forum: PC-Programmierung Thread Programmierung (vllt was peinlich)


von Rene B. (themason) Benutzerseite


Lesenswert?

Ich habe eine Frage bzgl Thread Programmierung. Ist mir zwar etwas 
peinlich dahingehend nachzufragen, da Thread Programmierung ja 
eigentlich von der Hand gehen "sollte", aber ich bin mir nicht sicher ob 
ich das richtig gemacht bzw richtig verstanden habe.

Ich möchte eine Konsolenanwendung schreiben die ganz klassisch eine 
Haupt-Schleife enthält. Nun ist es ja gar kein Thema einfach ein while 
(flag == FALSE) { blubb (); flag = ...; } zu programmieren. Funktioniert 
auch sehr gut (also das blubb steht dann für die Schleifenanweisung). 
Nur habe ich bedingt durch die endlos while schleife eben auch immer 
eine sehr hohe Prozessor-Auslastung. Da ich in den Anweisungen Audio 
verarbeite und diese Brute-Force Methode eben nicht das gelbe vom Ei 
ist, habe ich Störungen (z.b. bei einem Fenster-Wechsel, oder wenn ich 
mit dem Firefox surfe).
Nun möchte das Problem der hohen Auslastung (und der Störungen) durch 
Thread-Programmierung umgehen. Sprich, in meiner Thread-Funktion lasse 
ich dann die Hauptschleife laufen, und Erzeuge in der int main (...) den 
Thread.
Meine Frage ist nun folgende : Ich muß ja im Hauptprogramm (also in der 
main) dafür sorgen das das Programm nicht automatisch wieder verlassen 
wird (also das nach dem CreateThread direkt durch ein return 0 mir mein 
eig. Hauptprogramm beendet wird). Nun wäre es ja vollkommener Blödsinn, 
das Hauptprogramm durch eine while (1) { } Schleife am leben zu halten 
und im Thread die eigentliche aufgabe abzuarbeiten (sonst hätte ich ja 
direkt bei der Hauptschleife ohne Thread bleiben können, außerdem löst 
es nicht das Problem der Prozessorauslastung).
Mein zweiter Gedanke ist dann gewesen das ich ähnlich wie bei der 
Programmierung von Windows-Fenster-Applikationen in der Hauptschleife 
ein while (GetMessage (&msg, NULL, 0, 0)) { TranslateMsg (&msg); 
Dispatch (&msg); } zu machen, während der Thread im Hintergrund 
arbeitet.
Also das das Hauptprogramm Nachrichten empfangen kann (selbst wenn ich 
in der Konsolenanwendung keine richtige Nachrichten-Queue habe) und 
durch diese Abarbeitung am Leben gehalten wird.
Wenn ich eine reine Windows-Fenster-Applikation schreiben würde, wäre 
das ja auch richtig so (also Thread erstellen, Nachrichten Queue in 
Hauptschleife abarbeiten). Dadurch das ich aber eben eine 
Konsolenanwendung habe, und dadurch bedingt keine Nachrichten-Queue als 
solches (ich möchte insg. so wenig wie möglich Overhead haben um meine 
Applikation zu implementieren, und für diese Anwendung reicht eine 
Konsolenanwendung völlig aus) scheint mir das dennoch nicht der richtige 
Weg zu sein.
Also nochmal im kurzumriss :

Bisher :

int main (...)
{
  char flag = 0;

  vMainInit();

  while (flag == 0)
  {
    if (kbhit ())
    {
      if (getch () == 0x1b)
      {
        flag = 1;
      }
      else
      {
        ...
      }
    }
  }
  return 0;
}

Soll durch :

DWORD WINAPI ulThread (void *pvData)
{
  char flag = 0;

  vMainInit();

  while (flag == 0)
  {
    if (kbhit ())
    {
      if (getch () == 0x1b)
      {
        flag = 1;
      }
      else
      {
        ...
      }
    }
  }
  return 0;
}

int main (...)
{
  MSG msg;

  CreateThread (NULL, 0, &ulThread, 0, 0, 0);

  // hier ist der Abschnitt bei dem ich nicht weiß ob die "Gestaltung"
  // richtig ist.
  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }

}

ersetzt werden.

(Die eig. Anweisung auf einen Tastendruck zu reagieren ist nur 
beispielhaft, da in meiner Anwendung eben Tastendrücke wie 
Timer-Funktionen [über GetTickCount)] und eben auch Audio läuft)

Meine Frage (ums nochmal kurz zu machen) :

Ist die Hauptschleife in der main-Funktion brauchbar um die 
Konsolenanwendung am leben zu halten ? Oder gibt es da bessere Lösungen 
?
Ich habe mich zwar schon in Thread-Programmierung eingelesen (ich habe 
ein Buch über Windows-Programmierung da, aber da wird eben explizit auf 
Fenster-basierte Programmierung eingegangen und nicht eben 
Konsolenapplikationen, daher auch die Idee mit der Nachrichten-Queue)
Wie gesagt ursprünglich hab ich in der main Funktion den eigentlichen 
Hauptschleifen Code, aber durch das bedingte while (1) ist die 
Prozessorauslastung sehr hoch und es gibt Störungen (Pausen) bei der 
Abarbeitung des Programms.
Ich möchte (um ein etwaiges : "Warum schreibst du nicht eine 
Windows-Fenster-Anwendung ?" vorzubeugen) eine reine Konsolenanwendung 
haben weil ich so wenig wie möglich Overhead haben will.
Eine weitere Frage ist auch : Hätte ich wenn ich die Nachrichten-Queue 
in der Konsolenanwendung abarbeite die Möglichkeit auch per PostMessage 
Daten an diese Queue zu schicken, oder käme ich um ein RegisterWindow 
(wie es ja in der Windows-Programmierung erforderlich ist) nicht drum 
rum weil eine Konsolenanwendung per se keine Nachrichten-Queue hat ?

Es ist ein bissl schwammig formuliert aber ich kann hier nicht den 
kompletten Code Posten, zumal es sich eig. nur um die Hauptschleife 
geht, und wie man ein sauberes Abarbeiten in der Konsolenanwendung 
realisieren würde.

Hoffe diese Frage ist nich zu peinlich.

von Klaus W. (mfgkw)


Lesenswert?

hm, ich habe es jetzt zweimal gelesen und weiß immer noch nicht recht,
wie das Programm aussehen soll.

Es soll also eine Konsolenanwendung sein, ok.

Windows-Nachrichten soll es nicht verarbeiten.
Dann braucht man auch keine-Message-Schleife.

Die eigentliche Arbeit wird im Thread erledigt, das Hauptprogramm
hat nichts sinnvolles zu tun.
Dann kann man tatsächlich auch gleich den Code vom Thread ins
Hauptprogramm zurück verlagern. Das kann es also auch nicht sein,
sonst wärst du da schon selbst drauf gekommen.

Soll der Thread irgendwas arbeiten, während in main() nur
auf Tastatureingabeb gewartet wird, die dnan irgendwelche Flags
setzen sollen?

Dann könnte das so aussehen:
1
int main()
2
{
3
   CreateThread (NULL, 0, &ulThread, 0, 0, 0);
4
   while( "morgen ist auch noch ein Tag" )
5
   {
6
      switch( getch() )
7
      case 'a':
8
          flaga = true;
9
          break;
10
      case 'b':
11
          // ...
12
          break;
13
      default:
14
          // oioioi!
15
   }
16
}

Weil getch immer auf das nächste Zeichen wartet, verbrät main()
dann kaum Rechenzeit.

Will man nicht nur auf Tastendrücke warten, sondern auch noch
auf etwas anderes (z.B. abgelaufene Zeit, Zeichen von
serieller Schnittstelle etc.), darf getch() natürlich nicht
blockieren.
Dann könnte man etwa sowas bauen:
1
int main()
2
{
3
   CreateThread (NULL, 0, &ulThread, 0, 0, 0);
4
   while( "morgen ist auch noch ein Tag" )
5
   {
6
      // Ggf. auf Tastatur reagieren:
7
      if( kbhit() )
8
      {
9
        switch( getch() )
10
        case 'a':
11
            flaga = true;
12
            break;
13
        case 'b':
14
            // ...
15
            break;
16
        default:
17
            // oioioi!
18
     }
19
20
     // Ggf. auf etwas anderes reagieren
21
     if( anderesEreignis )
22
     {
23
         // ...
24
     }
25
     Sleep( 50 ); // kurzes Nickerchen schadet nicht
26
   }
27
}

Die Anzahl msec, die man bei jeden Durchlauf wartet, stellt
man so ein, daß auf Ereignisse ausreichend schnell reagiert
wird. Zu kleine Werte kosten dann mehr Rechenzeit.
Die Anzahl der Ereignisse pro Zeit, die man durchsetzen kann,
ist aber durch das Warten limitiert.
Das kann man so verbessern:
1
int main()
2
{
3
   CreateThread (NULL, 0, &ulThread, 0, 0, 0);
4
   while( "morgen ist auch noch ein Tag" )
5
   {
6
      // Ggf. auf Tastatur reagieren:
7
      if( kbhit() || anderesEreignis )
8
      {
9
        if( kbhit() )
10
        {
11
          switch( getch() )
12
          case 'a':
13
              flaga = true;
14
              break;
15
          case 'b':
16
              // ...
17
              break;
18
          default:
19
              // oioioi!
20
       }
21
22
       // Ggf. auf etwas anderes reagieren
23
       if( anderesEreignis )
24
       {
25
           // ...
26
       }
27
     }
28
     else
29
     {
30
       Sleep( 50 ); // kurzes Nickerchen schadet nicht
31
     }
32
   }
33
}
Dann wird nur geschlafen, wenn wirklich nichts anliegt.
Solange genug zu tun ist, wird kein Sleep() aufgerufen.

von Daniel (Gast)


Lesenswert?

Programmierst du unter Linux?
Dann schau dir mal die manpages von select und pthread an.

Ich würde mit select schlafen, bis das terminal
was empfängt und dann wacht select automatisch auf.

also so:

[c]
meinthreadfunktion() {
while(1)
 //mache das Audiozeug, schlafe so oft es geht!!
}

int main() {
pthread_init(meinthreadfunktion);
while(1) {
int k=select(/* siehe manpage*/)
if(k>0){
//Werte nutzereingabe aus
}
}

}

von Daniel (Gast)


Lesenswert?

nochmal der code:
1
meinthreadfunktion() {
2
while(1)
3
 //mache das Audiozeug, schlafe so oft es geht!!
4
}
5
6
int main() {
7
   pthread_init(meinthreadfunktion);
8
   while(1) {
9
      int k=select(/* siehe manpage*/)
10
      if(k>0){
11
         //Werte nutzereingabe aus
12
      }
13
   }
14
}

von Klaus W. (mfgkw)


Lesenswert?

naja, wenn er es schon mit TranslateMessage() und
DispatchMessage() probiert hat, wird es Windows sein.
Da geht select() leider nur für Sockets, nicht für andere
file-Deskriptoren.

von Daniel (Gast)


Lesenswert?

hmm, da würde mir nur noch cygwin einfallen, aber ich bezweifle dass das 
gewollt ist :)

von Tecnologic (Gast)


Lesenswert?

Moin,

Um mal auf den Kern des Problems zurück zu kommen, du willst eine 
Audiodatei bauen und hast Probleme, Wenn du neben dem Programm noch 
andere Programme verwendest? Wenn das so richtig ist, bist du mit 
Threads komplett falsch beraten, denn:

Windows setzt dein Programm selbst in einen Thread und gibt ihm 
anscheinend recht viel CPUzeit, wenn du jetzt ein 2. Programm auf hast, 
dann belegt dir das 1. Arbeitsspeicher und 2. braucht es Cpuzeit weil 
dieses ebenfalls in einen Thread gestartet wird.

Das Ende vom Lied ist ein 2. Thread in deinem Programm bringt rein gar 
nichts, du musst deinen Algorithmus überarbeiten denn der beansprucht zu 
viel Resourcen, ich tippe mal auf lange zusammenhängende 
Speicherbereiche im Ram, die wahrscheinlich nicht komplett reserviert 
sind, und bei Start eines weiteren Programmes wenn dir der 
Arbeitsspeicher knapper wird überschrieben werden könnten.

dh. vllt Pointerfehler oder Fehler in der Speicherresevierung.

das sind nur Vermuttungen ich kenne dein ganzes Programm nicht!!

von Rene B. (themason) Benutzerseite


Lesenswert?

erstmal danke für die antworten. ich glaube mein problem liegt aber wohl 
woanders. wie schon richtig erkannt arbeite ich unter windows.

mein problem ist wohl eher das ich im hauptprogramm nur mit polling 
arbeite und dadurch sehr viel zeit mit nichtstun verbrauche. daran würde 
auch ein thread nichts ändern.
das polling lässt sich nicht umgehen, da ich den gesamten quellcode per 
compilerschalter für den avr compilierbar machen möchte, und auf dem 
zielsystem eben keine event-abarbeitung im sinne von windows mit den 
nachrichten-queues habe, müssten da größere teile des codes geändert 
werden was aber das umschalten beim compilieren erschwert.
ich denke ich muß mit dem manko leben das ich enorm viel rechenzeit 
durch das polling verbrate. der thread kann da bestenfalls durch ein 
sleep (20); kurrzeitig schlafen gelegt werden, was vllt auch den 
rechenzeit-verbrauch etwas minimiert.

von Rene B. (themason) Benutzerseite


Lesenswert?

@Tecnologic

>Um mal auf den Kern des Problems zurück zu kommen, du willst eine
>Audiodatei bauen ...

nicht ganz.

um mal kurz mein ziel zu schildern :

ich bin dabei eine simulation des audio-projektes zu programmieren. da 
der zielprozessor ein avr kann ich natürlich nicht wie unter windows 
programmieren (sprich malloc, OOP, threads und dergleichen). also 
schreibe ich das programm für den avr und mache eine portierung auf 
windows (inkl der pgm_read_xxx aufrufe).
da der avr in einer endlosschleife läuft sollte das windows programm 
sich ähnlich verhalten, nur das unter windows eben in der hauptschleife 
noch die eig. simulationsaufgaben (simulation des DSPs mit 
audio-ausgabe, empfangen von tastatur-befehlen und umsetzung auf die 
avr-c-schnittstellen, kommunikation mit anderen programmen per mailslot 
und memory-mapped files um z.b. auch das auf dem zielsystem vorhandene 
touch-display zu simulieren, oder um direkt in die DSP-Simulation 
"reingucken" zu können [so soll es möglich sein eine art "tastkopf" auf 
ein bestimmtes audio-signal im dsp zu setzen, und in einem grafischen 
zusatzprogramm auf einem software-scope sichtbar zu machen], oder eben 
um in echt angeschlossene potis, schalter und knöpfe grafisch darstellen 
zu können und verwenden zu können) laufen.

ich denke bei dieser konstellation (und vor allem den anspruch den 
quellcode [abgesehen von der simulierten hardware und den 
schnittstellen] fast 1:1 verwenden zu können) ist es nur schwer möglich 
ein "waschechtes" windows-programm (also nachrichten-basiert) daraus zu 
machen ohne das es bei der compilation auf dem zielsystem zu zu großen 
änderungen kommt.

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Zu deinem eigentlichen Problem kann ich nichts sagen, aber mal eine 
andere Idee: wieso schreibst du nicht für DSP- und Steuerteil zwei 
getrennte Programme, die nur über ein Socket kommunizieren, und zwar im 
selben Befehlsformat das auch die Hardware verwendet? Wenn du dann statt 
dem Socket einen seriellen Port öffnest kannst du dann auch den echten 
DSP mit der PC-Software steuern, und den simulierten DSP mit der echten 
Steuerhardware. Erfordert evtl. etwas Rücksichtnahme auf das 
unterschiedliche Timing von Hardware und Simulation, aber wenn das 
robust läuft könnte es die Weiterentwicklung von beiden Teilen stark 
vereinfachen.

von Rene B. (themason) Benutzerseite


Lesenswert?

@andreas

ich war auch schon mit einer solchen aufteilung am überlegen, hab mich 
aber dafür entschieden dsp-simulation und steuerteil entweder komplett 
im uC oder in der simulation laufen zu lassen.
ich hab das aus dem grund gemacht da ich nachher eine übergeordnete 
pc-applikation haben möchte mit der ich die audio und steuermodule (im 
gesamtystem) untereinander verbinden kann. und diese pc-applikation 
würde dann wahlweise auf die simulation oder eben auf die reale hardware 
zugreifen. und wenn ich für die simulation dann noch 2 zusätzliche 
programme (ein dsp-simulationsteil und ein steuer-simulationsteil) 
benötige wär mir das zu umständlich, zumal der steuerteil sich 1:1 für 
win32 wie für den avr compilieren lässt und nur die eig. hardware 
simuliert werden muß und es somit nur wenig mehr aufwand ist.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.