Limit number of running threads with NewNamedSemaphore and GetSemaphore example

To limit the number of running threads in a application, this code can be used.

Please not that some templates and derived WindowManager classes don’t like being terminated by RETURN LEVEL:Notify from the Init() method. That is why this example is contained in a helper source procedure

Semaphore names must be unique, they share a system wide namespace.

#AT Global Embeds \ Inside Global Map

  MODULE('API')
    GetCurrentProcessId(),UNSIGNED,PASCAL
  END

Inside application Main (Frame) procedure:

#AT Local Data \ Generated Declarations

EmplLock             &ISemaphore
LOC:OpenLimit        LONG

#AT Local Objects \ Abc Objects \ Window Manager \ Init () DATA

LockErr             LONG

#AT Local Objects \ Abc Objects \ Window Manager \ Init () CODE, priority 7800

LOC:OpenLimit = 3 ! Or fetch from database
EmplLock &= NewNamedSemaphore('MyApp_' & GetCurrentProcessID() & '_MaximumThreads', LOC:OpenLimit, LOC:OpenLimit, LockErr)
IF EmplLock &= NULL THEN
    MESSAGE('Error NULL object preparing thread limit')
END
IF LockErr > WAIT:OK THEN
  MESSAGE('Error ' & LockErr & ' preparing thread limit')
  EmplLock.Kill()
  EmplLock &= NULL
ELSE
  EmplLock.Release(LOC:OpenLimit) ! Make 3 available
END

#AT Local Objects \ Abc Objects \ Window Manager \ Kill () CODE, priority 5050

IF NOT EmplLock&= NULL THEN
  EmplLock.Kill()
  EmplLock&= NULL
END

Inside helper thread start (Source) procedure:

MyApp_ProcedureLimit PROCEDURE()           ! Declare Procedure
EmplLock            &ISemaphore
LockErr             LONG

  CODE
  GlobalErrors.SetProcedureName('MyApp_ProcedureLimit')
  
  EmplLock &= GetSemaphore('MyApp_' & GetCurrentProcessID() & '_MaximumThreads', LockErr)
  IF EmplLock &= NULL THEN
    MESSAGE('Thread limit has not been initialized')
    DO ProcedureReturn
  END
  IF LockErr > WAIT:OK THEN
    MESSAGE('Error ' & LockErr & ' checking threadlimiet')
    DO ProcedureReturn
  END
  IF EmplLock.TryWait(100) = 1 THEN ! Reserve one
    EmplLock.Kill()
    EmplLock &= NULL
    MESSAGE('Maximum number of threads has been reached.|Close another thread first.')
    DO ProcedureReturn
  END
  
  MyApp_Procedure()

  IF NOT EmplLock &= NULL THEN ! Release one
    EmplLock.Release()
    EmplLock.Kill()
    EmplLock &= NULL
  END
  
  DO ProcedureReturn

ProcedureReturn     ROUTINE
  GlobalErrors.SetProcedureName()
  RETURN
1 Like

Alternative for: Inside helper thread start (Source) procedure:

EmplLock &= GetSemaphore('MyApp_' & GetCurrentProcessID() & '_MaximumThreads', LockErr)
IF EmplLock &= NULL THEN
  MESSAGE('Error NULL object preparing thread limit')
  DO ProcedureReturn
END
CASE LockErr
OF WAIT:TIMEOUT
  MESSAGE('Error #' & LockErr & ' Timeout checking thread limit')
  DO ProcedureReturn
OF WAIT:NOHANDLE
  MESSAGE('Error #' & LockErr & ' No handle checking thread limit')
  DO ProcedureReturn
OF WAIT:FAILED
  MESSAGE('Error #' & LockErr & ' Failed checking thread limit')
  DO ProcedureReturn
OF WAIT:OK
  IF EmplLock.TryWait(100) = 1 THEN
    EmplLock.Kill()
    EmplLock &= NULL
    MESSAGE('Maximum number of threads has been reached.|Close another thread first.')
    DO ProcedureReturn
  END
ELSE
  MESSAGE('Error #' & LockErr & ' Unknown checking thread limit')
  DO ProcedureReturn
END

Each time you start your EXE you will get a Unique GetCurrentProcessID() so using your code each EXE will have a Different Unique named Semaphore with a limit of 3. To work you need the same name for the Semaphore in every EXE, unless I missunderstand?

This version is designed to limit the number of threads per process (inside a application).

The code has been observed to return WAIT:NOHANDLE (2) in some situations even though no error occurred while creating the handle.

If the Semaphore only needs visibility inside the one EXE it does NOT need a Name so you could use NewSemaphore(LONG initial=0,LONG max=1). That way you have no possible name issues and the OS does not need to manage your named object.

You could probably do this simpler with a Global Long protected by a Critical Section, or Vista added the Slim Reader Write lock.

I was thinking of your other post: