Copyright (c) Hyperion Entertainment and contributors.

Exec Mutexes

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Exec Mutexes

Mutexes are similar to Exec Semaphores although the protocol defined on them is much simpler.

Mutexes can not be obtained shared or in an asynchronous fashion. If these features are required then a semaphore will likely be necessary. However, if the mutex isn't locked when an attempt is made to obtain it, it will acquire the lock without the use of Forbid/Permit locking. This puts less overhead on the system as a whole so a mutex should be preferred over a semaphore in most cases.

A mutex is also opaque. This means the programmer is not allowed to see the internals of a mutex. The reason for this design choice is to make it possible for the system to be able to break deadlocks when necessary and free mutexes automatically. SignalSemaphores are not opaque and may also be allocated statically which makes it impossible for the system to safely break a deadlock or free a semaphore automatically.

A mutex may be recursive or not. A recursive mutex is one which allows the same task which obtained it to obtain it again. In most cases, a recursive mutex is what is required so the default is to create a recursive mutex.

For mutexes to work correctly, there are two restrictions that must be observed at all times:

  1. All tasks using shared data that is protected by a mutex must always ask for the mutex first before accessing the data. If some task accesses the data directly without first going through the mutex, the data may be corrupted. No task will have safe access to the data.
  2. A deadlock will occur if a task that owns a mutex on some data inadvertently calls another task which tries to get the mutex on that same data. Deadlocks and other such issues are beyond the scope of this manual. For more details on deadlocks and other problems of shared data in a multitasking system and the methods used to prevent them, refer to Wikipedia.

Creating a Mutex

There is only one way to create a Mutex and that is via the AllocSysObject() function with an object type of ASOT_MUTEX. The mutex is an opaque pointer which is not open to the public.

Here is an example showing how to create a mutex:

APTR mutex = IExec->AllocSysObjectTags(ASOT_MUTEX,
  ASOMUTEX_Recursive, FALSE,  // The default is to create a recursive mutex.
TAG_END);

Obtaining a Mutex

The MutexObtain() function can be used to get a lock on a mutex. If another task currently has a lock on the mutex, your task will be put to sleep until all locks on the the mutex are released. If the same task tries to lock the mutex a second time the behaviour depends on whether the mutex was created with the recursive property or not.

Mutex Nesting
Mutexes have optional nesting. If the mutex is recursive, a lock by the same task on the same mutex will always succeed. If the mutex is not recursive, a lock by the same task on the same mutex will always fail. Which mode is most appropriate depends on the design of the application.

To obtain a mutex use:

APTR mutex;
IExec->MutexObtain(mutex);

Checking a Mutex

When you attempt to obtain a mutex with MutexObtain(), your task will be put to sleep if the mutex is not currently available. If you do not want to wait, you can call MutexAttempt(). If the mutex is available for locking, MutexAttempt() obtains it for you and returns TRUE. If it is not available, the function returns FALSE immediately instead of waiting for the mutex to be released.

The MutexAttemptWithSignal() function is similar to MutexAttempt() but will block if the mutex cannot be obtained. What makes MutexAttemptWithSignal() special is that it will unblock if the mutex is released or if a predefined signal is received. Being able to block on a mutex or a signal removes the need for any Forbid()/Permit() locking in such situations. If the mutex is available for locking, MutexAttempWithSignal() obtains it for you and returns a zero value. If it is not available and a signal is received, it will return the signal mask indicating which signal(s) were received.

To attempt to obtain a mutex, use the following:

APTR mutex;
IExec->MutexAttempt(mutex);

To make an attempt to obtain a mutex or a signal, the following code should be used:

APTR mutex;
uint32 sigmask;
 
IExec->MutexAttemptWithSignal(mutex, sigmask);

Releasing a Mutex

Once you have obtained the mutex and completed any operations on the mutex protected object, you should release the mutex. The MutexRelease() function does this. For each successful MutexObtain(), MutexAttempt() and MutexAttemptWithSignal() call you make, you must have a matching MutexRelease() call.

Removing a Mutex

Mutex resources can only be freed if the mutex is not locked. The FreeSysObject() function will not block your task if locks are still outstanding and it will result in undefined system behaviour.

IExec->FreeSysObject(ASOT_MUTEX, mutex);

Mutex Example

A simple "do nothing" example of Exec mutex use is shown below. When the mutex is owned by a task, attempted access by other tasks will block. A nesting count is maintained, so the current task can safely call ObtainMutex() on the same mutex.

// mutex.c - Exec mutex example - compile with gcc -o mutex mutex.c
#include <exec/exectags.h>
#include <proto/exec.h>
 
APTR LockMutex;
 
int main(int argc,char *argv[])
{
    LockMutex = IExec->AllocSysObjectTags(ASOT_MUTEX,
      ASOMUTEX_Recursive, TRUE,  // If set to FALSE, no nesting will be allowed.
      TAG_END);
 
    if (LockMutex != NULL)
    {
        IExec->MutexObtain(LockMutex);  // Task now owns the mutex.
        // ...
        IExec->MutexRelease(LockMutex); // Task has released the mutex.
 
        IExec->FreeSysObject(ASOT_MUTEX, LockMutex);
    }
 
    return 0;
}

Function Reference

The following charts give a brief description of the Exec semaphore functions. See the SDK for details about each call.

Exec Mutex Function Description
AllocSysObject(ASOT_MUTEX) Allocate and initialize a new semaphore.
MutexAttempt() Try to get an exclusive lock on a mutex without blocking.
MutexAttemptWithSignal() Try to get an exclusive lock on a mutex or a signal mask.
FreeSysObject(ASOT_MUTEX) Free a mutex.
MutexObtain() Try to get exclusive access to a mutex.
MutexRelease() Release the lock on a mutex.