Search code examples
multithreadingdelphifreepascallazarus

Make threads wait for conditon in other object


If I want to limit the number of threaded lookups that can be done in a lexicon simultaneously, how to do that? And have the threads sleep until they can have access to the lexicon. ie. If two lookups are already going on in parallell, other lookups must wait and not be performed until LookupCount is less than two.

TLexicon
  MaxLookups: Integer;
  LookupCount: Integer;

Besides TLexicon, I have created TLookupJob which is suposed to be executed within TLookupThread. The LookupJob.Execute procedure calls lookup on the lexicon and waits until there is a response.

So, LookupThreads must sleep until Lexicon.LookupCount is less than Lexicon.MaxLookups. To this end I call GoSleep (event) in LookupJob.Execute and the execution of the LookupThread halts. But how and when to signal it. The rest of the design is unclear to me.

What design could be used here?

So far I have created a number of classes:

TLexicon - Has MaxLookups and LookupCount, plus a Lookup function.

LookupItem - Contains LookupWord, and Response. Passed to Lexicon.Lookup.

TLookupJob - Has an Execute procedure, plus GoSleep an WakeUp procedures that uses an event for sleep.

LookupThread - Executes the LookupJob

Feel free to change the design.


Solution

  • If you're targetting the Windows platform, WinAPI provides suitable synchronisation functions. See CreateSemaphore, ReleaseSemaphore, WaitForSingleObject. (Of course a platform independent option may be preferred, but this may still prove useful in some environments.)

    When you create/initialise your lexicon, create a sempaphore as follows:

    FSemaphoreHandle := CreateSemaphore(nil, MaxLookups, MaxLookups, nil);
    

    Then when doing a lookup, use the semaphore to limit how many threads get into the main body of the lookup routine at the same time.

    function TLexicon.Lookup(...): ...;
    begin
      //The wait function will block if the current counter = 0 and wait
      //until another thread releases a counter.
      WaitForSingleObject(FSemaphoreHandle, <timeout>);
      //When the wait function returns (assuming due to semaphore count
      //available and not timeout), the current counter of the semaphore
      //is decreased. This mechanism limits the number of threads that
      //can get past the wait funtion.
      try
        //Do lookup
      finally
        //By releasing the semaphore, the current counter is increased again,
        //allowing a blocked thread to get past the wait function.
        ReleaseSemaphore(FSemaphoreHandle, 1, nil);
      end;
    end;
    

    And finally, don't forget to CloseHandle when you're done with the lexicon.

    NOTE: This is a bare bones example. Don't forget to add appropriate error checking. E.g. WaitForSingleObject indicates if the semaphore could be obtained.