Frontier Tutorials / Working With Threads / Semaphores--Traffic Control for Threads

Semaphores--Traffic Control for Threads

A semaphore (sometimes called a mutex, or mutual-exclusion lock) is a flag used to indicate that a routine cannot proceed if a shared resource is already in use by another routine. By locking a semaphore, a routine can block other routines that share the same resource. If the semaphore is already locked when a routine attempts to lock it, the routine halts (ceases processing) until the semaphore is unlocked by whatever other routine or process locked it, at which time the first routine finishes locking the semaphore for its own use and resumes its processing.

Don't Throw Away the Key

Because of the behavior of semaphores, you must be careful to avoid a deadlock condition. Deadlock occurs when a process locks a semaphore, then later tries to lock the same semaphore again. The process cannot resume until the semaphore is unlocked, and cannot unlock the semaphore until it can resume processing--so the process never resumes. At the same time, any other process that depends on the same semaphore will halt when it tries to lock the semaphore.

Deadlock can also occur when one routine locks a semaphore, then calls itself recursively or calls another routine that recursively calls the first routine.

When the first routine tries for a second time to lock the semaphore, it stops dead in its tracks.

Unlock the Door When You Leave

Because only the process that has the locked semaphore can use a shared resource, you should keep your locked code blocks as small as possible. That will minimize the amount of time your routines spend waiting for the resource. Imagine the chaos that would ensue if you insisted on keeping the bathroom door locked even when you weren't in there!

Semaphores in Frontier

In Frontier, the verbs semaphore.lock and semaphore.unlock are used to lock and unlock semaphores, respectively. Semaphores are identified by name (a string), and all processes requiring access to a given shared resource must use the same semaphore. It is not uncommon to use an ODB address for the semaphore name, but that is just for convenience; the semaphore name may be any string. For example, a semaphore to lock a shared table system.temp.sharedTable could be called "system.temp.sharedTable", or it could be called "George"; either would work the same.

In earlier versions of Frontier (prior to 5.1), the semaphore verbs were to be found at system.verbs.builtins.semaphores. They are now at system.verbs.builtins.semaphore--the "s" has been dropped from the name of the table. The two are otherwise identical. For compatibility in older versions of Frontier, you can duplicate system.verbs.builtins.semaphores and rename the copy "semaphore". For compatibility in newer versions of Frontier, you can duplicate system.verbs.builtins.semaphore and rename the copy "semaphores".

A third semaphore verb, semaphore.unlockAll, has little utility within your scripts, because (as the name implies) it unlocks all semaphores currently locked in the Frontier environment. That includes yours, the Betty webserver's, the inetd internet monitor daemon's, etc. This could be a Bad Thing.

On the other hand, semaphore.unlockAll is very useful while developing and debugging threaded scripts, because threads may not be automatically unlocked when you kill your script to make changes.

In Frontier 5.1.4, semaphores are properly unlocked when they go out of scope due to a scriptError being thrown. However, in earlier versions of Frontier, this was not the case. For compatibility with earlier versions of Frontier, you should wrap code that locks and unlocks semaphores in a try/else block, and call semaphore.unlock from within the else clause.

Dead of Old Age

When you call semaphore.lock, you pass it a timeout interval as well as a semaphore name. For most purposes, a timeout interval of 3600, or 60 seconds, is used. Depending on your application, it could be nearly any value. (If it's above a few minutes, though, you should probably be using Frontier's scheduler or an agent.) If the semaphore is already locked when you call semaphore.lock and is not unlocked within this timeout interval, Frontier will throw a scriptError to inform you that the semaphore has timed out.

If there is a possibility of a semaphore timeout in your application, you should catch the scriptError and deal with it in your code.

Timeouts are common, for example, in all communication applications. A timeout may indicate that a shared resource is unavailable, or that a process (either the one that experienced the timeout or any other running process) has become deadlocked.

Now let's see how to be thread-friendly.

Tutorial Contents
Working With Threads
What Are Threads?
Semaphores--Traffic Control for Threads
How to Be Thread-Friendly
Rules of Thread Safety
An Example
Frontier's Thread Verbs
Thread Utilities
Glossary of Terms
About the Author