[Delphi] Threads en locken

Pagina: 1
Acties:
  • 470 views sinds 30-01-2008
  • Reageer

Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
Ik ben bezig met een projectje waarin een hoop threads werken die ieder een eigen taak uitvoeren. Deze taken moeten gelijktijdig worden uitgevoerd en zijn niet afhankelijk van elkaar.

Elke thread moet taak, status en voortgang doorgeven aan het mainform. Om interface/form en threads van elkaar te scheiden wil ik werken met een soort Fifo-lijst waarin elke thread steeds aangeeft wat hij in de interface als nieuwe taak/status enz. wil hebben.
Deze lijst wordt dan door een aparte thread continue leeg gehaald en middels synchronize() verwerkt in de mainform. Dit heeft het voordeel dat er maar 1 thread bezig is met de interface. En dat de andere threads dus nooit hoeven te wachten daarop.

Daarvoor zijn 2 systemen beschikbaar een TList in de vorm van TThreadlist en een systeem met messages.

De tweede heb ik geen ervaring mee en ook nog geen duidelijke uitleg gevonden op internet.Waar kan ik die vinden?

De eerste ligt het dichtst bij met wat ik in gedachten heb, en is inhoudelijk heel goed te debuggen en controleren. Echter ik zit met enkele punten die ik niet kan vinden in de documentatie.
• Er is een lock/unlock-methode, maar is die ook nodig bij het add-en van een object?
• In de docu staat dat remove zelf al een lock/unlock uitvoert. Hoef ik dat dan niet meer te verzorgen?
• Is het een bruikbare oplossing voor een situatie waarin max 15 threads de interface willen bijwerken (dus schrijven in TThreadlist) en er maar 1 thread leest (en dus verwijderd uit TThreadlist)?
• Ik zoek eigenlijk naar het tegenovergestelde van TMultiReadExclusiveWriteSynchronizer. Is er zoiets.

Acties:
  • 0 Henk 'm!

Anoniem: 14829

Leesvoer
Misschien wat oud, maar het dekt wel zo ongeveer alles wat je bij multithreaded proggen in Delphi kunt tegenkomen af.

Acties:
  • 0 Henk 'm!

  • Paul
  • Registratie: September 2000
  • Laatst online: 14:41
Klinkt als een klassier producer / consumer probleempje waarbij iedere thread producer is van statusinformatie, en de consumer de GUI bijwerkt?
Semaphores are your friend iirc :)

Het enige wat ik zelf tot dusver heb gedaan aan GUI-updating deed ik door een message naar de main-thread te gooien waarin ik de tekst in een TStatusBar veranderde :P

Ik weet niet wat je precies moet updaten, Labels zijn volgens mij wel goed aan te passen, tekst in een TMemo oid aanpassen zou ik niet weten, omdat dat weer via een TStrings-object gaat :P

Edit: Hmm, toch eens kijken of ik nog ooit een kopietje van die code kan bemachtigen, ik vond het nifty van mezelf destijds :P

[ Voor 11% gewijzigd door Paul op 23-02-2005 23:24 ]

"Your life is yours alone. Rise up and live it." - Richard Rahl
Rhàshan - Aditu Sunlock


Acties:
  • 0 Henk 'm!

Anoniem: 25556

Zoek even op de volgende woorden in de delphi help:

Synchronize
VCL Lock method
Mutex
TCriticalSection

Als je er niet uitkomt, geef even een gil.. ik heb hier het boek van Marco Cantu voor me liggen, waarin jouw vraag als voorbeeld behandelt wordt. Maar voor dat ik die 4 pagina's ga overtypen, mag je zelf even zoeken ;)

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 11:01

Tomatoman

Fulltime prutser

Wat hier nog niet is toegelicht, is het gebruik van messages. Er zijn twee manieren om messages te versturen, namelijk via SendMessage en PostMessage. Bij SendMessage wordt de message helemaal vooraan in de message queue geplaatst en direct afgehandeld. Als de functie die SendMessage aanroept verder gaat, is de message reeds afgehandeld. Bij PostMessage wordt de message daarentegen helemaal achteraan in de message queue geplaatst. Het programma gaat verder zonder te wachten tot de message is afgehandeld. Het programma krijgt op een gegeven moment vanzelf tijd om de volgende message uit de queue te behandelen. Dat gaat net zolang door tot de met PostMessage message verstuurde message aan de beurt is. Dat kan dus even duren.

Bij het versturen van een message moet je de handle specificeren van de control waarnaar je de message verstuurt. Normaliter gebruik je een TWinControl descendant als control, omdat die standaard functionaliteit bezit om messages te verwerken. Vaak is het handig om je main form te gebruiken om de messages af te handelen.

Even uit de losse pols (ongetest):
Delphi:
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
const
  WM_TELLER = WM_USER;
  WM_KLAAR = WM_USER +1;

type
  TWMTeller = record
    Msg: Longint;
    Tellerwaarde: Longint;
    Unused: Longint;
    Result: Longint;
  end;

  TForm1 = class(TForm)
  private
    procedure WMTeller(var Msg: TWMTeller); message WM_TELLER;
    procedure WMKlaar(var Msg: TMessage); message WM_KLAAR;
  end;

procedure TForm1.WMTeller(var Msg: TWMTeller);
begin
  Caption := 'Tellerwaarde: ' + IntToStr(Msg.Tellerwaarde);
end;

procedure TForm1.WMKlaar(var Msg: TMessage);
begin
  Caption := 'Klaar';
end;

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type
  TMijnThread = class(TThread)
    procedure Execute; override;
  end;

procedure TMijnThread.Execute;
var
  i: Integer;
begin
  for i := 0 to 1000 do
    if not Terminated then
    begin
      // doe hier iets interessants
      PostMessage(Form1.Handle, WM_TELLER, i, 0);
    end;
  PostMessage(Form1.Handle, WM_KLAAR, 0, 0);
end;

Een goede grap mag vrienden kosten.


  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
tomatoman schreef op donderdag 24 februari 2005 @ 13:30:
[KNIP zeer interressant verhaal]

Delphi:
1
  PostMessage(Form1.Handle, WM_TELLER, i, 0);
Dit ziet er zeer interressant uit, maar is het ook mogelijk om meer info dan een LongInt door te geven. Bijv. een string, een record of een object?

Anoniem: 30225

jvdmeer schreef op donderdag 24 februari 2005 @ 18:16:
[...]
Dit ziet er zeer interressant uit, maar is het ook mogelijk om meer info dan een LongInt door te geven. Bijv. een string, een record of een object?
Alleen als je SendMessage gebruikt.
Als je hiervoor PostMessage gebruikt dan hangt het af van wie de string, record, object, etc. heeft aangemaakt. Op het moment dat de message wordt afgehandeld zou het object namelijk al weer opgeruimd kunnen zijn.

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Maar met SendMessage block je de thread totdat de message afgehandeld is, dus ben je het effect van je thread kwijt. Het kan ook met PostMessage, als je maar duidelijk maakt dat de ontvangende kant de boel weer moet vrijgeven.

We adore chaos because we like to restore order - M.C. Escher


Anoniem: 25556

Sendmessage, postmessage, etc, zijn allemaal helemaal niet nodig om een aantal threads te laten syncen met een form, hoor..

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Nee, maar dat zegt ook niemand. Tomatoman laat alleen nog een manier zien om threads en GUI met elkaar te laten samenwerken. Het voordeel hiervan is dat het een gratis queue is. De threads hoeven nooit op elkaar te wachten en de GUI handelt de events af wanneer het er tijd voor heeft.

We adore chaos because we like to restore order - M.C. Escher


  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
Anoniem: 25556 schreef op donderdag 24 februari 2005 @ 20:11:
Sendmessage, postmessage, etc, zijn allemaal helemaal niet nodig om een aantal threads te laten syncen met een form, hoor..
Welke suggestie heb jij? In de link die Afterlife gaf, liep ik al tegen de BoundedBuffer aan die met 2 of meer threads werkt. Dit lijkt overkill voor wat ik wil. Jij noemt een boek van Cantú. Ik heb hier 'Mastering Delphi 7' en ik kan me niet herinneren dat ik stof hierover tegenkwam.

De oplossing met messages lijkt de simpelheid te hebben die ik zoek. Enige nadeel vindt ik dat er een hwnd bekend moet zijn van de taak die de messages afhandeld. Dit betekent volgens mij (cmiiw) dat de messagehandler altijd een onderdeel moet zijn van de form-declaratie, terwijl ik dat stuk liever in een aparte unit had gezet.

[ Voor 2% gewijzigd door jvdmeer op 24-02-2005 22:14 . Reden: d/t + regel aangepast. ]


  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Je kan met AllocateHWnd in elke class een message handler maken, dus ook in een aparte unit.

We adore chaos because we like to restore order - M.C. Escher


Anoniem: 25556

Mastering Delphi 7 heb ik uitgeleend, dus ik kan je niet zo zeggen of het er in staat, en op welke pagina. Mastering Delphi 6 zie ik het zo snel niet in de index staan, dus het kan zijn dat het in de latere boeken vervallen is.

Uit mastering Delphi 5 (ja ik spaar die boeken ;):
We have seen that there are two common approaches for synchronizing a thread with the rest of the application: using the Synchronize method of a thread object and using the Lock method of a VCL class that provides it, such as TCanvas.

Waiting for a thread

When a thread should wait until another thread is done, it can simply call the WaitFor method of the object corresponding to the thread that should terminate. Here is a portion of an example, in which a program starts a thread and then wait for its result:
Delphi:
1
2
3
4
5
6
Comp : = TmyThread.Create(True);
// initialize the thread....
Comp.Resume;
Comp.WaitFor;
// look for final values ....
Comp.Free

This code is quite simple to write, but remember that you cannot write this code as part of the main thread (for example, in a normal message-response function) if the secondary thread has to synchronize with it. If the thread connected to the main form is waiting for another thread to finish, and the secondary thread is waiting to access the user interface (hence waiting for the main thread to finish its current job), the program wil enter a deadlock!

To avoid this problem, you can use WaitFor to syncrhonize two threads. A first thread creates a secondary thread, and waits for it to end, without interfering with the main form's thread.

To show you an example of synchronization with multiple threads, I've built a character counting program, called ThWait. The program computes how many copies of the four characters specified in an edit box are present in the text of a Memo component (as an alternative, you can load the text from any file, as long as you do so before starting the computation). The program looks for each of the four characters at the same time, using multiple threads spwaned by the main thread. To improve the output, each thread shows its status - that is, how far through the text it has searched - in a progress bar, as you can see in Figure 17.4.

Afbeeldingslocatie: http://www.tweakers.net/ext/f/52589/full.jpg
figure 17.4, let niet op de crappy kwaliteit, met een goede cam genomen door een slechte fotograaf met slecht licht

Because memo lines are fetched using a sendmessage to the control's window handle, and a window's message are processed in the context of the main thread, in practice there is no advantage to using four threads to scan through the text of the same memo control. Still, the example demonstrates some usefull techniques.

The actial engine of the program is the TFindThread class, which contains the LookFor field to hold the character we are searching for and a Progress field to store the value of the progress bar and update its status. The result of the computation is placed in the public Found field:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
type
  TFindThread = class(TThread)
  protected
    Progr: Integer;
    procedure UpdateProgress;
    procedure Execute;override;
  public
    Found: Integer;
    LookFor: Char;
    Progress: TProgressBar;
  end;

As usual, the core of the thread is in its execute method, which scans the lines of the memo, looking for the given character. Notice that we can freely access the properties of the memo without synchronization, since this operation is a nondestructive read - it doesn't affect the status of the Memo component:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TFindThread.Execute;
var
  I, J : Integer;
  Line: String;
begin
  Found := 0;
  With Form1.Memo do
    for i:=0 to Lines.Count -1 do 
      begin
        Line := Lines[i];
        for j:=1 to length(line) do
          if Line[j] = Lookfor then
            Inc(Found);
        Progr := I+1;
        Synchronize (UpdateProgress);
      end;
end;

The updateProgress method simply updates the status of the progress bar using the value of the field Progr:
Delphi:
1
2
3
4
procedure TFindThread.UpdateProgress;
begin
  Progress.Position := Progr;
end;      

Four copies of this thread are activated by the primary thread, an object of the TMultiFind class. Here is the declaration of this class:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
type
  TMultiFind = class(TThread)
  protected
    Progr: Integer;
    procedure Updateprogress;
    procedure Execute; override;
    procedure Show;
  public
    Lookfor, Output: String;
    Progresses: array [1 .. 5 ] of Tprogressbar;
  end;

This thread class looks for the characters of the LookFor string (there must be four characters for the program to work correctly), using four TFindThread objects:
Delphi:
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
procedure TMultiFind.Execute;
var
  Finders: Array [ 1 .. 4 ] of TFindThread;
  I: Integer;
begin
  // set up the four threads
  for I:= 1 to 4 do
    begin
      Finders[I] :=TFindThread.Create(true);
      Finders[i].LookFor := LookFor[i];
      Finders[i].Progress := Progresses [I+1];
      Finders[i].Resume;
    end;
  // wait for the threads to end ...
  for i := 1 to 4 do
    begin
      Finders[i].WaitFor;
      Progr := i;
      Synchronize (UpdateProgress);
    end;
  // show the result
  Output := 'Found: ';
  For i:=1 to 4 do
    Output:= Output + Format ('%d %s, ', [Finders[I].Found, LookFor[i]]);
  Synchronize (Show);
  //delete threads
  for i:= 1 to 4 do
    Finders[i].free;
end;

Notice in particular the for loop with the waitfor call. At the end, the Execute method shows the result in a synchronized method, Show. The program I've written to test these threads is quite simple. As you saw in Figure 17.4, it has a memo where you can load a file and an edit box containing the four characters. The number of characters is checked when the user exits the edit box:
Delphi:
1
2
3
4
5
6
7
8
procedure TForm1.Edit1Exit(Sender: TObject);
begin
  if Length (Edit1.text<>4) then
    begin
      Edit1.SetFocus;
      ShowMessage ('the edit box requires four characters');
    end;
end;

The STart button starts the thread, which in turn immediatly spawns the secondary threads:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  If Assigned(MainThread) then
    MainThread.Free;
  MainThread := TMultifind.Create (true); 
  MainThread.Progresses [1] := ProgressBar1;
  MainThread.Progresses [2] := ProgressBar2;
  ....
  Mainthread.Progresses[1].Max : = 4;
  For i:=2 to 5 do
    MainThread.Progresses[i].Max := Memo1.lines.Count;
  for i:= 1 to 5 do
    MainThread.Progresses[i].Position := 0;
  MainThread.LookFor := Edit1. Text;
  MainThread.Resume;
end;


Notice that we cannot delete the thread at the end of the method, because we cannot call waitfor on it without creating a deadlock. Take care when writing multithreaded applications, because such a deadlock can freeze the whole system, leaving you with no option but to press CTRL-ALT-DELETE.

At the same time, to keep the operating system stable, we must remember to delete the thread, either before creating a second one (as at the beginning of the code above) or when the program terminates. This is also the reason we need to declare the thread object as a private field of the form and not as local variable of the method starting it.
Tot zover nu even. Het 2e voorbeeld volgt nog, die past al beter bij wat je wilt. Hij werkt met mutexen.

[ Voor 18% gewijzigd door Anoniem: 25556 op 27-02-2005 20:26 ]


  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
Anoniem: 25556 schreef op donderdag 24 februari 2005 @ 22:38:
AUB nog niet op deze lap tekst reageren voordat ik klaar ben. Het zijn twee voorbeelden, waarvan met name het tweede van belang is ;)
Bij deze alvast bedankt voor de vele moeite. En zover ik kan zien staat dit stuk inderdaad niet in Mastering Delphi 7. Volgens mij gaat deze thread een mooi naslagwerkje worden dankzij jou.

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

jvdmeer schreef op woensdag 23 februari 2005 @ 22:02:
Elke thread moet taak, status en voortgang doorgeven aan het mainform. Om interface/form en threads van elkaar te scheiden wil ik werken met een soort Fifo-lijst waarin elke thread steeds aangeeft wat hij in de interface als nieuwe taak/status enz. wil hebben.
Deze lijst wordt dan door een aparte thread continue leeg gehaald en middels synchronize() verwerkt in de mainform. Dit heeft het voordeel dat er maar 1 thread bezig is met de interface. En dat de andere threads dus nooit hoeven te wachten daarop.

Daarvoor zijn 2 systemen beschikbaar een TList in de vorm van TThreadlist en een systeem met messages.
Misschien een hele stomme vraag, maar wat is exact het probleem met TThread.Synchronize() die je nu gebruikt? Als in: dat mechanisme is exact hiervoor bedoeld, is automatisch FIFO en werkt perfect. Ik zie het probleem niet waarom je in godesnaam via andere mechanismes zou willen werken :)

Professionele website nodig?


Anoniem: 25556

Goed, dan hier deel twee..
Windows Synchronization Techniques

The Win32 API functions offer many further synchronization techniques:
Critical Sections are portions of source code that cannot be executed by two threads at the same time. By using a critical section, you can serialize the executeion of specific portions of the source code. Critical sections can be used only within a single process, a single application.
Mutexes are global objects you can use to serialize access to a resource. You first set a mutex, then access the resource, and finally release the mutex, as we saw in the OneCopy example. While the mutex is set, if another thread (or process) tries to set the same mutex, it is stopped until the mutex is released by the previous thread (or process). A mutex can be shared by different applications.
Semaphores are similar to mutexes, but they are counted: yu could allow, for example, three and no more than three accesses to a given resource at the same time. A mutex is equivalent to a semaphore with a maximum count of 1. We'll see an example of the use of the semaphore in the section "threaded database acces" (dus niet - hezik) later in this chapter.
Events can be used as a mean of synchronizing a thread with system events, such as user file operations. The WaitFor method of the Delphi TThread class uses an event. Events can also be used to awaken several threads at the same time.

Building an Example

To demonstrate all of these different techniques I've built the ThSynch example. Suppose we have two threads operationg on a string, both of them using its value in some way and then updating the string as a result of the operation. Suppose also that the current value of the string is shared by the two threads. In example the string intially contains 20 A characters, then is update to contain 20 B characters, and so on. Each thread simply computes the next value of the string and then send it to its own list box.

The ThSynch example presents a main form with four buttons, each of hich displays a secondary form demonstrating one of the syncrhronization techniques. Each secondary form consists of two list boxes, where the text is displayed in a nonproportional Courier font, and a button to start the related thread. So we end up with four different versions of basically the same form and the same thread class. In each of these forms the STart button simply creates two instances of a thread, associating a list box with the LBox public field of each:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
procedure TForm2.BtnStartClick(Sender: TObject);
  begin
    Listbox1.Clear;
    Listbox2.Clear;
    Th1 := TListThread.Create(True;
    Th2 := TListThread.Create(True;)
    Th1.FreeOnTerminate := True;
    Th2.FreeOnTerminate := True;
    Th1.Lbox := Listbox1;
    Th2.Lbox := Listbox2;
    Th1.Resume;
    Th2.Resume;
end;


Notice that the FreeOnTerminate property is set to True, so that the thread object will automatically free itself when its execution is completed. The thread classes are declared in each of the units defining the form, to avoid having to many files. This same unit contains the string variable used by the two thread objects:
Delphi:
1
2
var
  Letters : string ='AAAAAAAAAAAAAAAAAAAA';


This is far from elegant, but in this program we are looking for trouble on purpose, so forget the coding style.

The Plain Thread

Here is the thread class and its execute method in a first simple version:
Delphi:
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
type
  TListTread = class(TThread)
  private
    Str: String;
  protected
    procedure AddToList;
    procedure Execute; override;
  public
    LBox: TListbox;
  end;

procedure TListThread.Execute;
var
  I,J,K: Integer;
begin
  for i := 0 to 50 do
    begin
      for j = 1 to 20 do
        for k=1 to 2601 do // useless repetition ...
          if Letters [J] <> 'Z' then
              Letters[J] := Succ(Letters[J])
            else
              Letters[J] := 'A';  
      Str := Letters;
      Synchronize (AddToList);
    end;
end;


The AddToList method simply adds the Str string to the list box connected with the trhread. I've made each computation artifically long, by increasing each letter 2601 times instead of once: The effect is the same, but there are more chances of having a conflict between the two threads. You can see this effect in figure 25.5. Even better, you can press the Start button two or three times in a row, starting several threads at once, and increasing the chance of errors.

Afbeeldingslocatie: http://www.tweakers.net/ext/f/52598/full.jpg

Using Critical Sections

If you want to serialize the operations of the two threads, and have more control over what the threads do, you can use one of the Windows Synchronization techniques i've discussed before. In this second version I'll use critical sections. To do this you should add the declaration of another global variable (or a form class field) for the critical section:
Delphi:
1
2
var
  Critical1: TRTLCriticalSection;

This variable is initialized when the form is created, and it is destroyed at the end with two api calls:
Delphi:
1
2
3
4
5
6
7
8
9
procedure TForm3.FormCreate(Sender: TObject);
  begin
    InitializeCriticalSection (Critical1);
  end;

procedure TForm3.FormDestroy (Sender: TObject);
  begin
    DeleteCriticalSection (Critical1);
  end;

You can use critical sections to serialize specific portions of the code, such as the code that updates each letter of the string. Here is how I've updated the code of the Execute method of the thread object:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TListThread.Execute;
var
  I,J,K: Integer;
begin
  for i := 0 to 50 do
    begin
      EnterCriticalSection (Critical1);
      try
        for j = 1 to 20 do
          ... //same code as above
        Str := Letters;
      finally
        LeaveCriticalSection (Critical1);
      end;
      Synchronize (AddToList);
    end;
end;

The effect of this code is that while one thread is computing a new string the other thread will wait before doing the same, so all the output strings will always contain 20 copies of the same character.

Using a mutex

Now we can write the same code, but using a mutex instead of a critical section. The effect will be the same, but I'd like to show you this technique as well. We simple need to declare the hMutex variable of type THandle and then replace the four API calls of the previos example with the following four:
Delphi:
1
2
3
4
5
6
7
8
// in formcreate
hMutex := CreateMutex (nil, false, nil);
// in formdestroy
CloseHandle (hMutex);
// in the for loop
WaitForSingleObject (hMutex, INFINITE);
...
ReleaseMutex(hMutex);

And this concludes our session of 'see how quick Hezik can type over a book' :)
Bij deze alvast bedankt voor de vele moeite. En zover ik kan zien staat dit stuk inderdaad niet in Mastering Delphi 7. Volgens mij gaat deze thread een mooi naslagwerkje worden dankzij jou.
Graag gedaan, hopelijk heb je er iets aan :)

[ Voor 42% gewijzigd door Anoniem: 25556 op 25-02-2005 00:14 ]


Acties:
  • 0 Henk 'm!

  • FendtVario
  • Registratie: Januari 2002
  • Laatst online: 12-05 22:30

FendtVario

The leader drives Vario!

Niet alleen naslag voor de TS ;). De stukken die je hebt gegeven komen volgens mij voor 98% overeen met Mastering Delphi 4 (het enige deel dat ik heb). Samen met de link van Afterlife wat ook al 70 pagina's tekst is kan iedereen die met threads aan de gang wil weer vooruit.

Hier nog een linkje over semaphoren.

www.fendt.com | Nikon D7100 | PS5


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
curry684 schreef op donderdag 24 februari 2005 @ 23:16:
Misschien een hele stomme vraag, maar wat is exact het probleem met TThread.Synchronize() die je nu gebruikt? Als in: dat mechanisme is exact hiervoor bedoeld, is automatisch FIFO en werkt perfect. Ik zie het probleem niet waarom je in godesnaam via andere mechanismes zou willen werken :)
Het detail zit 'm in:
jvdmeer schreef op woensdag 23 februari 2005 @ 22:02:
Om interface/form en threads van elkaar te scheiden wil ik werken met een soort Fifo-lijst waarin elke thread steeds aangeeft wat hij in de interface als nieuwe taak/status enz. wil hebben.
Dit stukje code is nog niet gemaakt, omdat ik meer info zocht over mogelijkheden van synchroniseren.

Bijv. bij Hezik's eerste stuk van Cantú beschouw ik het als een probleem dat ik in de thread een TProgressbar moet opnemen. Want misschien wil ik in een later stadium de TProgressbar wel vervangen door een ander control. Hiervoor moet ik dan de synchronize-methode van de thread herschrijven. Het liefst hanteer ik nl. scheiding van code en interface.

Hiervoor bedacht ik dan een fifo-lijst. Met daarin hoogstens berichtjes in de trant van 'Thread 5 doet nu ....' of 'Voortgang thread 9 is nu 89%' enz...
Op deze manier hoeft de thread niets te weten van het form/control waar de status op wordt bijgewerkt.
De enige thread die er nu iets vanaf moeten weten is de thread die de fifo leeghaalt en de interface bijwerkt.

Kort samengevat uit de verschillende info zijn er een paar methoden:
synchronize: de code wordt uitgevoerd door de main-thread en de tthread blijft wachten. Bij meer threads die synchronize gebruiken wordt fifo gehanteerd.
CriticalSection: de code wordt uitgevoerd door tthread en de tthread blijft wachten. Bij meer threads die CriticalSection gebruiken wordt fifo gehanteerd.
Mutex: de code wordt uitgevoerd door tthread en de tthread blijft wachten. Bij meer threads die dezelfde mutex gebruiken wordt fifo gehanteerd.
Semaforen: de code wordt uitgevoerd door tthread en de tthread blijft wachten. Bij meer threads die dezelfde semafoor gebruiken wordt lifo gehanteerd.
Messages: de code wordt uitgevoerd door de main-thread op het moment dat de main-thread dat wil en de tthread blijft wel/niet wachten. Bij meer threads die een message gebruiken wordt fifo gehanteerd.

• CriticalSection en Mutexes werken op dezelfde wijze, wat ook blijkt uit het tweede stukje van Cantú door Hezik.

Ik had eerst op basis van het artikel van Afterlife een versie op basis van TBoundedBuf (d.m.v. mutex), echter nadat ik de code zag van tomatoman heb ik het herschreven naar messages. De code werd daardoor veel simpeler.

Voorlopig heb ik dus tthreads die m.b.v. new() een record aanmaken en de pointer doorgeven via een message.
De message wordt verwerkt door de form en daarna roept de form de dispose aan van het record.
Per WM_USER+x hanteer ik een andere recorddefinitie.

Enige nadeel tot op heden is dat de verwerking van de messages nu in de module van het form zitten, en dat de threads op de hoogte moeten zijn van de hwnd van het form. Deze laatste geef ik dus maar door via de constructor.

[ Voor 1% gewijzigd door jvdmeer op 25-02-2005 08:03 . Reden: spelfoutje ]


Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

Hoe dan ook is zuivere synchronizatie hopeloze overkill hiervoor, en zelfs niet handig te implementeren. Dit soort 'lazy' communicatie moet je via Synchronize() doen of desnoods via WM_USER messages, maar zelfs dat vind ik overbodig omslachtig. Je hebt je GUI en processinglagen gescheiden, je scheduled alleen een GUI-updater functie dmv Synchronize en doet feitelijk geen GUI functionaliteit.

Als je het dan perse omslachtig wil doen, maak gewoon een threadshared string voor 'current status' die je met TCriticalSection dichthoudt, en update die vanuit alle threads. Vervolgens kun je in de OnIdle van de GUI-thread het ding updaten (natuurlijk ook even locken). FIFO-queues zijn echt hopeloos overkill.
jvdmeer schreef op vrijdag 25 februari 2005 @ 08:02:
[...]

• CriticalSection en Mutexes werken op dezelfde wijze, wat ook blijkt uit het tweede stukje van Cantú door Hezik.
Ze zijn dan ook hetzelfde: een CriticalSection is een light-weight process local unnamed Mutex. Let wel dat dit niet voor TCriticalSection geldt: de VCL implementatie is een singleton static global Critical Section wat niet de meest efficiente locking oplevert als je op meer plaatsen lockt. Het voorkomt gelukkig wel per definitie dead- en livelocks voor de minder ervaren devver :)

[ Voor 31% gewijzigd door curry684 op 25-02-2005 08:41 ]

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

jvdmeer schreef op vrijdag 25 februari 2005 @ 08:02:
Enige nadeel tot op heden is dat de verwerking van de messages nu in de module van het form zitten, en dat de threads op de hoogte moeten zijn van de hwnd van het form. Deze laatste geef ik dus maar door via de constructor.
Dat hoeft dus niet:
LordLarry schreef op donderdag 24 februari 2005 @ 22:35:
Je kan met AllocateHWnd in elke class een message handler maken, dus ook in een aparte unit.

We adore chaos because we like to restore order - M.C. Escher


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
Had ik gezien, maar nog niet naar bekeken.Ga ik zeker nog naar kijken.
curry684 schreef op vrijdag 25 februari 2005 @ 08:39:
Hoe dan ook is zuivere synchronizatie hopeloze overkill hiervoor, en zelfs niet handig te implementeren. Dit soort 'lazy' communicatie moet je via Synchronize() doen of desnoods via WM_USER messages, maar zelfs dat vind ik overbodig omslachtig. Je hebt je GUI en processinglagen gescheiden, je scheduled alleen een GUI-updater functie dmv Synchronize en doet feitelijk geen GUI functionaliteit.
De oplossing tot nu toe bevalt dan ook redelijk. De oplossing met producer/worker-threads kwam op mij ook al ingewikkeld over. Maar toen wist ik niet van de messages.Deze WM_USER-messages bieden mij nu wat ik zoek op een eenvoudige manier. (Incl. fifo-functionaliteit) Maar het belangrijkste vind ik dat de threads niet op de interface of op elkaar hoeven te wachten, want daar lagen mijn twijfels.

Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

jvdmeer schreef op vrijdag 25 februari 2005 @ 09:13:
[...]

Maar het belangrijkste vind ik dat de threads niet op de interface of op elkaar hoeven te wachten, want daar lagen mijn twijfels.
Ah, dus daar zit de denkfout :)

Synchronize wacht helemaal nergens op, maar queued een User APC (Asynchronous Procedure Call) in de thread context van de mainthread. Er vindt dus geen wait plaats, die code wordt gewoon prioritized uitgevoerd bij de volgende thread switch naar de main thread, en de calling thread yield meteen op het moment dat je die call plaatst. Er is dus geen waitstate :)

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
curry684 schreef op vrijdag 25 februari 2005 @ 09:32:
[...]

Ah, dus daar zit de denkfout :)

Synchronize wacht helemaal nergens op, maar queued een User APC (Asynchronous Procedure Call) in de thread context van de mainthread. Er vindt dus geen wait plaats, die code wordt gewoon prioritized uitgevoerd bij de volgende thread switch naar de main thread, en de calling thread yield meteen op het moment dat je die call plaatst. Er is dus geen waitstate :)
Jammer dat sommige documentatie dat wel noemt, bv maar niet uitsluitend het leesvoer van afterlife, hoofdstuk 3. Zeker gezien de afbeelding onderaan de pagina:
Afbeeldingslocatie: http://www.pergolesi.demon.co.uk/prog/threads/fig4.gif
en dan ook nog een quote uit de tekst:
Chapter 3. Basic synchronization.
When synchronize is called, the prime calculation thread is suspended. At this point, the main VCL thread may be suspended in the idle state, it may be suspended temporarily on I/O or other operations, or it may be executing. If it is not suspended in a totally idle state (main application message loop), then the prime calculation thread keeps waiting. Once the main thread becomes idle, the parameterless function passed to synchronize executes in the context of the main VCL thread. In our case, the parameterless function is called UpdateResults, and plays around with a memo.
of de delphi-help:
[url=ms-help://borland.bds3/bds3win32api_win32/html/ClassesTThreadSynchronizeMethod.htm]TThread.Synchronize Method[/url]
Synchronize causes the call specified by Method to be executed using the main thread, thereby avoiding multi-thread conflicts. If you are unsure whether a method call is thread-safe, call it from within the Synchronize method to ensure that it executes in the main thread.

Execution of the thread current is suspended while Method executes in the main thread.

Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

Suspended is een erg groot woord :)

Ja, de caller thread is suspended, echter dat is enkel totdat de APC is uitgevoerd. In de praktijk zal de yield inhouden dat de target thread redelijk direct aan de beurt komt (priorities voorbehouden), dan direct z'n APC uitvoert, en de suspension opgeheven is. Er zal in de praktijk naast de yield zelf geen kernelmode wait plaatsvinden. Je moet dan wel inderdaad zorgen dat je in die APC niet 3 minuten bezig bent, maar alleen even doet wat je hoognodig moet doen en zo snel mogelijk oprot ;)

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
curry684 schreef op maandag 28 februari 2005 @ 00:10:
Suspended is een erg groot woord :)

Ja, de caller thread is suspended, echter dat is enkel totdat de APC is uitgevoerd. In de praktijk zal de yield inhouden dat de target thread redelijk direct aan de beurt komt (priorities voorbehouden), dan direct z'n APC uitvoert, en de suspension opgeheven is. Er zal in de praktijk naast de yield zelf geen kernelmode wait plaatsvinden. Je moet dan wel inderdaad zorgen dat je in die APC niet 3 minuten bezig bent, maar alleen even doet wat je hoognodig moet doen en zo snel mogelijk oprot ;)
Bedankt voor deze nadere uitleg. Ik moet ook maar uitgaan van wat ik aan documentatie vind.
Als ik het dus goed begrijp, maakt het gebruik van Synchronize en het gebruik van messages niet veel uit in performance, echter bij messages kan je een object/parameter etc meegeven, wat bij synchronize echt niet mogelijk is.

Dus als ik een variabele tekst/waardewil meegeven met synchornize moet dat op de volgende wijze:

Bijv (uit de losse pols):
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Thread=class(TThread)
    procedure Execute;
    procedure DoSynchronize;
  private
    TmpMessage:string;
    procedure Sync(Msg:string);
end;

procedure Thread.Execute
begin
  while not terminated do
    sleep(1000)
end

procedure Thread.Sync(Msg:string);
begin
  TmpMessage:=Msg;
  Synchronize(DoSynchronize);
end;

procedure Thread.DoSynchronize
begin
  Label1.Caption:=TmpMessage;
end;


Terwijl ik via messages de string via een record/pointer rechtstreeks kan doorgeven aan TForm om daarna op basis van de message label1.caption kan bijwerken en de pointer/string vrijgeven.

Vind ik de tweede wijze duidelijker

[ Voor 2% gewijzigd door jvdmeer op 01-03-2005 19:32 . Reden: Even overloading weggehaald, was uit de losse pols. ]


Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

Gezien het feit dat Synchronize per definitie serialiseert kun je gewoon een member variabele of een global gebruiken voor data passing :)

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
curry684 schreef op dinsdag 01 maart 2005 @ 00:01:
Gezien het feit dat Synchronize per definitie serialiseert kun je gewoon een member variabele of een global gebruiken voor data passing :)
Kun je dat kort toelichten? Ik gebruik in mijn voorbeeld toch een membervariabele? Gaat het gebruik van globals geen problemen opleveren?

Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 27-05 16:00

curry684

left part of the evil twins

Holy fuck je overload Synchronize? :X Zoek eens snel die helpfiles op, aan Synchronize moet je een functiepointer meegeven :X

Verder doe je de data passing wel goed ;)

[ Voor 6% gewijzigd door curry684 op 01-03-2005 00:45 ]

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • jvdmeer
  • Registratie: April 2000
  • Laatst online: 27-05 21:37
curry684 schreef op dinsdag 01 maart 2005 @ 00:45:
Holy fuck je overload Synchronize? :X Zoek eens snel die helpfiles op, aan Synchronize moet je een functiepointer meegeven :X

Verder doe je de data passing wel goed ;)
Sorrie, was uit de losse pols. Heb het even verbeterd in mijn voorbeeld.
Pagina: 1