TEvent, Mutex and Semaphores for multithreading...need some code example

Hi guys.
I am learning mulithreading in delphi  and I would like to know if some of you
could post me some basical example (not how use threads) on how using mutex, Tevent, and semaphore in order to syncronize code in thread Excute method and thread's events and how check the signaled state with WaitForSingleObject?

Is it reasonable to use semaphore for limiting the number of running threads?

For exmaple if I want to limit the max running threads may i use this?
CreateSemaphore (nil, 20,20,'MyS');

And what happen if one thread get timeout or can't access to resource?  It's lost or it will be put in Wait state ?

thx


jaja2005Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ziolkoCommented:
Hi this is pretty big topic:)

when writting multithreaded apps first thing you have to take care of is protecting resources that can be accessed by more than one thread. there are few ways to do it but most popular is using critical section.

other thing is limitting number of threads (called thread pooling) in this case semaphore is most natural choice.

before I paste any code samples/more comments I have one question: what experience do you have with working with threads? did you used then at all in past?

ziolko.
0
jaja2005Author Commented:
Hi ziolko.
Thanks for your reply.
Yes, I know the main idea of thread and i have used them for creating some simple applcation using Tthread delphi class.
You know the more you code and more question comes to you . So I browsed the web for searching info and
I fonud some tutorial and have red it. By the way it's seems that it's quite difficult to get a good resource
that explain everything from A to Z and step by step. Thread seems a secret topic...:-)
0
ziolkoCommented:
yeah it's not easy to explain everything:)
but no it's not a secret topic:) it's just that there are many ways and little things that you must be aware of so most important thing is to define your problem a then looke for answer for that specific problem. if you want "general information" well we can discuss it over and over again and probably other experts will post their ideas... alomst endless story:)

but I'll try edscribe basicis in few words:

first of all synchronization objects are OS objects so they exist in OS kernel not in your app. most of the have wrappers into delphi objects but still they are OS objects.

mutex - it is basic synchronization object it can be in one of two states" signaled and not-signaled it works as a gate. it can be opened or closed. it can be used to protect shared resource but most developers use it when they want make sure that only one instance of their app is running. to do so first thing that app does when is launched it tries to create named mutex, if it is created sucessfully app loads normally, but if creating mutex fails it means that someone else (other instance) already created it so you can display information that multiple instances are not allowed and don't let your app to start as second instance

critical section - pretty similar to mutex. this is first choice when you want protect shared resources. for example if you have integer variable that can be read and/or changed by more that one thread. typically you will use this construction:

  FSynchro.Enter;
  try
    // read or change shared resource
  finally
    FSynchro.Leave;  
  end;

where FSynchro is TCriticalSection

.Enter means that thread which executes this code "eneters room and closes door behind itself" so other threads can not get in and must wait (microsoft calls it "effective wait state" meaning that waiting thread is not taking much of processor cycles and general OS performance is not slowed down)
.Leave means that thread is done so it "opens the door" for others, then first waiting thread can get in and do it's job.

just remember that it is important which thread executes code not where this code is implemented.

semaphore - it's some kind of extended mutex or "a gate with security guy" making sure that only fixed number of threads may go thru this gate, if there is too many candidates semaphore lets only few of them while rest must wait (again "effective wait state")

event is also simillar to mutex, the difference is that mutex is released when process/thread is releasing it's handle while changing state of mutex is done explicitly (by calling SetEvent() or ResetEvent())

now to open some of your original questions:

>>Is it reasonable to use semaphore for limiting the number of running threads?
yes, semaphore is perfect for this

>>how check the signaled state with WaitForSingleObject?
WaitForSingleObjedct will return appropriate result, MSDN says:

WAIT_ABANDONED
0x00000080L The specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled.
If the mutex was protecting persistent state information, you should check it for consistency.
 
WAIT_OBJECT_0
0x00000000L The state of the specified object is signaled.
WAIT_TIMEOUT
0x00000102L The time-out interval elapsed, and the object's state is nonsignaled.

so if you called WaitForSingleObject(handle to object, INFINITE)
and return is WAIT_OBJECT_0 it means that object is not-signalled and it's "your turn" to do job on protected resource

if you called WaitForSingleObject(handle to object, some_time) and result is WAIT_TIMEOUT it means that your thread waited some_time but was not able to access protected resource it's up to you what thread should do in this case, just remember that this thread is no longer in effective wait state

ziolko.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

ziolkoCommented:
here is basic sample with semaphore of course there is no appropriate error checking but I'm sure you can add that easily:

if you run this and analyze what's in memo you will see that even if there are 11 threads created
only 3 threads are working at same time, but as soon as one of them terminates next one comes alive

type

  TOnThreadAction = procedure(AThreadID: Integer;AValue: Integer) of object;

  TSampleThread = class(TThread)
  private
    FOnAction: TOnThreadAction;
    FInternalID: Integer;
    FSemaphore: THandle;
  protected
    procedure Execute;override;
    procedure LaunchAction(AValue: Integer);
  public
    constructor Create(CreateSuspended: Boolean;AInternalID: Integer;ASemaphore: THandle);overload;
    property OnAction: TOnThreadAction read FOnAction write FOnAction;
  end;

{ TSampleThread }

constructor TSampleThread.Create(CreateSuspended: Boolean;AInternalID: Integer;ASemaphore: THandle);
begin
  inherited Create(Suspended);
  FInternalID := AInternalID;
  FreeOnTerminate := True;
  FSemaphore := ASemaphore;
end;

procedure TSampleThread.Execute;
var cnt: Integer;
    loop: Integer;
begin
  if WaitForSingleObject(FSemaphore, INFINITE) = WAIT_OBJECT_0 then begin
    OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, 'samplethread');
    Randomize;
    loop := Random(10);
    for cnt := 0 to loop do begin
      LaunchAction(cnt);
      Sleep(1);
    end;
    LaunchAction(-1);
    ReleaseSemaphore(FSemaphore, 1, nil);
  end;
end;

procedure TSampleThread.LaunchAction(AValue: Integer);
begin
  if Assigned(FOnAction) then
    FOnAction(FInternalID, AValue);    
end;

procedure TForm1.OnThreadAction(AThreadID: Integer;AValue: Integer);
begin
  Memo1.Lines.Add(Format('Thread no.: %d sent: %d', [AThreadID, AValue]));
end;

procedure TForm1.Button6Click(Sender: TObject);
var t: TSampleThread;
    cnt: Integer;
begin
  Memo1.Clear;
  FSemaphore := CreateSemaphore(nil, 3, 3, 'samplethread');

  for cnt := 0 to 10 do begin
    t := TSampleThread.Create(False, cnt, FSemaphore);
    t.OnAction := OnThreadAction;
  end;
 
end;

ziolko.
0
ziolkoCommented:
of course ReleaseSemaphore should be wrapped in try/finally, but as I said this is very basic sample

ziolko.
0
jaja2005Author Commented:
THanks a lot.

I will review you suggestion ASAP in waiting for code examples with mutex and TEvent.

;-)
0
ziolkoCommented:
mutex sample:

program Project1;

uses
  Forms,
  Windows,
  Dialogs,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

var mutex: Cardinal;

begin
  Application.Initialize;
  mutex := CreateMutex(nil, True, 'testmutex');
  if (GetLastError = ERROR_ALREADY_EXISTS) then begin
    ShowMessage('instance already running');
  end else begin
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;
end.

ziolko.
0
ziolkoCommented:
for event... here is small extraction from my code used to listen for changes in registry:

procedure TEventWaitThread.Execute;
var
  notif: Boolean;
  reg: TRegistry;
begin
  reg := TRegistry.Create;

  try
    reg.Access  :=
    reg.RootKey :=
    reg.OpenKey(FRegPath, False);

    notif := False;

    FEvent := CreateEvent(nil, False, False, PChar(Format(REGISTRY_EVENT_NAME, [FName])));
    notif  := RegNotifyChangeKeyValue(reg.CurrentKey, False, REG_NOTIFY_CHANGE_LAST_SET, FEvent, True) = ERROR_SUCCESS;

    TestAPICalls;

    while (FEvent <> INVALID_HANDLE_VALUE) and (notif) and (not Terminated) do
    begin

      if WaitForSingleObject(FEvent, INFINITE) = WAIT_OBJECT_0 then
      begin

        if not FClosing then
        begin
          PostMessage(FMsgRcv, REGISTRY_MESSAGE, 0, 0);

          ResetEvent(FEvent);  



ziolko.
0
ziolkoCommented:
....
destructor TEventWaitThread.Destroy;
begin
  FClosing := True;

  SetEvent(FEvent);

  if FEvent <> INVALID_HANDLE_VALUE then
    CloseHandle(FEvent);
   
  inherited Destroy;
end;

ziolko.
0
ziolkoCommented:
and once again first sample with semaphore, but extended with critical section to prevent shared resource (in this case shared resource is memo as more than one thread may try to write to it)

type

  TOnThreadAction = procedure(AThreadID: Integer;AValue: Integer) of object;

  TSampleThread = class(TThread)
  private
    FOnAction: TOnThreadAction;
    FInternalID: Integer;
    FSemaphore: THandle;
  protected
    procedure Execute;override;
    procedure LaunchAction(AValue: Integer);
  public
    constructor Create(CreateSuspended: Boolean;AInternalID: Integer;ASemaphore: THandle);overload;
    property OnAction: TOnThreadAction read FOnAction write FOnAction;
  end;

{ TSampleThread }

constructor TSampleThread.Create(CreateSuspended: Boolean;AInternalID: Integer;ASemaphore: THandle);
begin
  inherited Create(Suspended);
  FInternalID := AInternalID;
  FreeOnTerminate := True;
  FSemaphore := ASemaphore;
end;

procedure TSampleThread.Execute;
var cnt: Integer;
    loop: Integer;
begin
  if WaitForSingleObject(FSemaphore, INFINITE) = WAIT_OBJECT_0 then begin
    OpenSemaphore(SEMAPHORE_ALL_ACCESS, True, 'samplethread');
    Randomize;
    loop := Random(10);
    for cnt := 0 to loop do begin
      LaunchAction(cnt);
      Sleep(1);
    end;
    LaunchAction(-1);
    ReleaseSemaphore(FSemaphore, 1, nil);
  end;
end;

procedure TSampleThread.LaunchAction(AValue: Integer);
begin
  if Assigned(FOnAction) then
    FOnAction(FInternalID, AValue);    
end;

procedure TForm1.OnThreadAction(AThreadID: Integer;AValue: Integer);
begin
  FSynchro.Enter;
  try
    Memo1.Lines.Add(Format('Thread no.: %d sent: %d', [AThreadID, AValue]));
  finally
    FSynchro.Leave;  
  end;
end;

procedure TForm1.Button6Click(Sender: TObject);
var t: TSampleThread;
    cnt: Integer;
begin
  FSynchro := TCriticalSection.Create;  
  Memo1.Clear;
  FSemaphore := CreateSemaphore(nil, 3, 3, 'samplethread');

  for cnt := 0 to 10 do begin
    t := TSampleThread.Create(False, cnt, FSemaphore);
    t.OnAction := OnThreadAction;
  end;
 
end;


ziolko.
0
jaja2005Author Commented:
great, let u know.
thx
0
SteveBayCommented:
ziolko
[off topic]
Welcome back. I just received notice of this post today
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_24036296.html
I really don't understand why it should take almost 6 months for me to be notified of a new post.

[slightly more on topic]
The stuff you have written above is (in my opinion) perfect material for an article. While I have nothing to add to what you have said here I found it an interesting read. It's a topic that even very experienced programmers don't understand all that well.
You ought to consider editing it up a little and publishing it here.
Thanks.

0
ziolkoCommented:
Hi Steve, you didn't have to wait 6 months for notification, last post I made today :)

as about threads and stuff... it's true that you can write an article about that or even more than one article:)
that's why it's so difficult to find one good resource because either it will not cover whole problem or it will be so big that reading it would be a nightmare.

that's why it's better to have specific problem and find good solution for it, good example is situation if you want create thread-pool of let's say 20 threads. you should take different approach when those 20 threads  will be used to complete 100 tasks in short ammount of time every 5 minutes and different if those tasks appear constantly every few miliseconds, also aproximation how long it wil ltake a task to complete should be considered when deciding which way to go. plus there are those tiny things that can make a big difference, some time ago I made service app which in some circumstances killed performance of a server afetr 3 weeks of problem tracing I found out that it was caused by using Sleep(0) instead of Sleep(1)

ziolko.


0
Dagan HooverDeveloperCommented:
I agree with SteveBay. I've done some thread work in the past but have tried to avoid it due to complexities. I learned quite a bit from this question =P.
0
ziolkoCommented:
so how many points do I get from both of you?;)

note that synchronization is one thing but related to it is also interesting problem how to exchange data between threads, that's topic for few more articles:)

ziolko.
0
SteveBayCommented:
More articles = more points!
0
ziolkoCommented:
>>More articles = more points!

yeah I know but I think I'm too lazy to write a big text which must be easy to understand with correct english and some logic:)

I thought about setting up website with articles for medium-experienced... maybe some day:)

ziolko.
0
jaja2005Author Commented:
Ziolko, are you italian?
Zio=uncle
0
Geert GOracle dbaCommented:
on the synchronisation ...
i tried the articles section:
http://www.experts-exchange.com/articles/Programming/Languages/Pascal/Delphi/Displaying-progress-in-the-main-form-from-a-thread-in-Delphi.html

>ziolko
you'll have to ask for a lot of points to catch up again ...
0
ziolkoCommented:
jaja2005
no, i'm not italian

geert
I'm not in hurry:) step by step I'll get there:)

ziolko.
0
jaja2005Author Commented:
Hi Ziolko.
I have reviewed you code and everything is ok, you have done a great job, very good examples.
Just one question regarding the use of Sampahore for the message with ID:24492008.
The question is not directly releated with using of semaphore...why you have used a
type procedure TOnThreadAction instead of Syncronize method?

Thx
0
Geert GOracle dbaCommented:
for syncronize the main thread will process the procedure

with a semaphore you will just protect the data block for 1 thread
you won't have to wait on the main thread to do it
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.