Entwickler-Ecke

Windows API - Semaphoren und Threads (und Events)


Gausi - Di 13.04.10 18:36
Titel: Semaphoren und Threads (und Events)
Ich hab da eine kleine Verständnisfrage zu Semaphoren. Ich habe eine Workerthread-Klasse, die so aussieht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
type 
  TCoverDownloadWorkerThread = class(TThread)
    private
      fSemaphore: THandle;
      //...  
  end;


constructor TCoverDownloadWorkerThread.Create;
begin
  // ...
  fSemaphore := CreateSemaphore(Nil0, maxInt, Nil);
  // ...
end;

procedure TCoverDownloadWorkerThread.Execute;
begin
  While Not Terminated do
  begin
    if (WaitforSingleObject(fSemaphore, 1000) = WAIT_OBJECT_0) then
     if not Terminated then
     begin
       machwas;
     end;
  end
end;


Ich erzeuge also eine Semaphore, und warte darauf, dass da was passiert, bevor der eigentliche Thread anfängt zu arbeiten. Wenn was zu tun ist, rufe ich von der VCL aus das auf:

Delphi-Quelltext
1:
2:
3:
4:
procedure TCoverDownloadWorkerThread.StartWorking;
begin
    ReleaseSemaphore(fSemaphore, 1Nil);
end;

Von meinem Verständnis her habe ich damit einen Thread, der schön im Hintergrund schlummert, bis er angestoßen wird oder terminieren soll.

Jetzt sieht das im Ereignisprotokoll aber so aus, als würden da ständig neue Threads laufen. Mach ich da was falsch, oder ist das normal so? D.h. die ID des Worker-Threads wechselt bei so einer Anwendung?

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
Thread-Start: Thread-ID: 4064. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 4064. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 3012. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 3012. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 1204. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 1204. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 1364. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 1364. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 3552. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 3552. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 196. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 196. Prozess nemp.exe (3720)
Thread-Start: Thread-ID: 2376. Prozess nemp.exe (3720)
Thread-Ende: Thread-ID: 2376. Prozess nemp.exe (3720)
// für jeden Job ein Thread, immer einer nach dem anderen


Kha - Di 13.04.10 19:14

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Von meinem Verständnis her habe ich damit einen Thread, der schön im Hintergrund schlummert, bis er angestoßen wird oder terminieren soll.
So würde ich es doch auch sehen. Von daher ist es halbwegs beruhigend, dass uns wenigstens der folgende Code Recht gibt ;) .

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Windows;

type
  TMyThread = class(TThread)
  public
    fSemaphore : THandle;
    constructor Create;
  protected
    procedure Execute; override;
  end;

{ TMyThread }

constructor TMyThread.Create;
begin
  inherited Create(True);
  fSemaphore := CreateSemaphore(nil0, MaxInt, nil);
end;

procedure TMyThread.Execute;
begin
  inherited;
  while WaitForSingleObject(fSemaphore, INFINITE) = WAIT_OBJECT_0 do Writeln('a');
end;

var t : TMyThread;
begin
  t := TMyThread.Create;
  t.Start;
  while True do
  begin
    ReleaseSemaphore(t.fSemaphore, 1nil);
    Sleep(1000);
  end;
end.


Gausi - Di 13.04.10 19:31

Ok, der Code verhält sich bei mir auch nicht so. :D

Nachdem ich mal diese beiden Zeilen auskommentiert habe

Delphi-Quelltext
1:
2:
3:
XMLData := fIDHttp.Get(url);
// ...
fIDHttp.Get(BestCoverURL, DataStream);

Ist das auch im Originalcode so.

Frage also: warum machen die Indys das so, und kann man das ggf. abstellen, falls das hier nicht sinnvoll ist? Der Download läuft ja schon in einem Nebenthread, da muss doch nicht noch einer gestartet werden. :gruebel:


Martok - Di 13.04.10 19:34

Eigentlich nimmt man für diesen Zweck ja Events :roll:

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Der Download läuft ja schon in einem Nebenthread, da muss doch nicht noch einer gestartet werden. :gruebel:

Doch. Irgendwo muss ja der MessageLoop hin, mit dem die Indies das schöne Nachrichtenbasierte WSA-Gedöns synchron machen.

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
und kann man das ggf. abstellen, falls das hier nicht sinnvoll ist?

Ich würde also sagen, nein.


Gausi - Di 13.04.10 19:48

Ok, dann ist das wohl so in Ordnung. :)

Zu den Events: Wie würde da denn das Grundgerüst aussehen? Was ich sonst in der Richtung Workerthread gesehen habe, arbeitet iirc auch mit einer Semaphore. Und der Delphi-TThread hat doch keine Nachrichtenschleife, in die ich was einhängen könnte, oder?


Narses - Di 13.04.10 19:52

Moin!

Du suchst nicht sowas [http://www.delphi-forum.de/viewtopic.php?t=90333], oder? :gruebel:

cu
Narses


Martok - Di 13.04.10 20:03

@Narses: nein, meine ich nicht. Ich meine die Synchronisationsobjekte "Event". Obwohl man sicher auch TMessageThread für die Anwendung nehmen könnte... wäre zu überlegen.

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Zu den Events: Wie würde da denn das Grundgerüst aussehen?

Fast genauso, nur dass man statt CreateSemaphore CreateEvent verwendet.

Und dann später SetEvent oder PulseEvent. Code hab ich auch irgendwo, ich kopier mal was raus... ah, okay, da war ein VCL-Fan am Werk. Unit SyncObjs.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
//Von außen:
  FFeedEvent:= TEvent.Create(nil, true, false,'');
  FFeed:= TWOFeed.Create(Self, FFeedEvent);


// wo anders:
  if NochBufferDa then
    Verarbeiten;
  else begin
    FFeedEvent.SetEvent;
    exit;
  end;


procedure TWOFeed.Execute;
var b: integer;
begin
  b:= 0;
  while not Terminated do begin
    FEvent.WaitFor(100);
    FEvent.ResetEvent;
    try
      FOwner.Thread_SendFunc(b);
    except
      raise;
    end;
  end;
end;


Also quasi das Gleiche. Nur das Events wesentlich schneller sind, was die Zeit von SetEvent bis zum Zurückkehren von WaitFor* betrifft. Liegt unter anderem da dran, dass sie für dieses Thema gemacht sind.
Und wir reden hier IIRC von Mikrosekunden vs. Millisekunden.


Gausi - Di 13.04.10 20:17

Ah, ok. Wieder was gelernt. :D

Den MessageThread behalte ich mal im Hinterkopf, und diese Form der Events auch. Das sind also nicht die Events, die man an den Komponenten kennt, sondern globale Dinger, richtig?

Aber um Geschwindigkeiten mach ich mir hier aber keine Gedanken. Erstens soll der Thread was runterladen (erst eine kleine REST-Anfrage, dann ein kleines Bild, im Schnitt 50-100 kb). Der Flaschenhals liegt also ganz woanders. Und zweitens darf ich das sowieso nur maximal fünf mal pro Sekunde machen, sonst schimpfen die bei LastFM. ;-)

Aber ich probier das mal mit den Events aus, das scheinen ja nur ein paar kleine Änderungen zu sein, die ich da machen muss.

Edit: ich müsste dann ein if FEvent.WaitFor(100)=wrSignaled then... einbauen, wenn nur dann etwas getan werden soll, wenn das Event vorher ausgelöst wurde, oder?

Edit2: Ok, mit Events geht das genauso gut. Und wenn die für sowas da sind, dann lass ich das jetzt auch so. :D