Mutual Exclusion (Mutex): Overview

A mutex (mutual exclusion service) is a mechanism to control thread execution. This mechanism can prevent multiple threads from executing simultaneously in particular places in the program and from simultaneously accessing data, registers, and other resources.

Initializing Mutexes

A mutex is initialized with the OS_InitMutex function. One mutex can be configured for each OSMutex structure object.

Mutex Structure

The OSMutex structure is as follows.

struct OSMutex
{
    OSThreadQueue   queue;
    OSThread*       thread; // the current owner
    s32             count;  // lock count (notice: use upper 1 byte as mutex type)

    OSMutex*        prev;   // link for OSThread.queueMutex
    OSMutex*        next;   // link for OSThread.queueMutex
};


The members prev and next manage mutex lists, but queue, thread, and count compose the actual mutex synchronization feature.

queue is the thread queue where the thread that is being made to wait by mutex is registered.

thread is the member that registers the current thread that is using this mutex to block other threads.

count is the member that manages the lock nest for this mutex. It counts the number of times the lock has been set. However, the upper 1 byte is used to indicate the mutex type. The remaining 24 bits are used for the count value. The type is included in count instead of setting it to a separate member in order to retain compatibility with previous versions of mutex. (Note: Because the emphasis is on speed, the upper limit of the count value is not checked. One mutex can use 24 bits, which is equal to roughly 1.667 million nested levels. That limit will probably never be reached.)

The following section explains mutex types.

Mutex Types

The mutex types are STD, R, and W. (These are defined in the header as OS_MUTEX_TYPE_STD and so on. In the following explanation, they are referred to as STD, R, and W.) If no type is specified, NONE is used.

For normal thread synchronization, an STD mutex is used. With this type of mutex, other threads are not permitted unconditional entry.

The R and W types are called read-lock and write-lock mutexes.

The read-lock mutex does not allow other threads to perform write or standard mutex locks. This type can be used when data is read. Multiple threads can simultaneously read the data without problem, but a data-writing process at this point could compromise the data consistency.

The write-lock mutex can be used when a thread is writing data. The data to be written cannot come from multiple threads. The W mutex does not permit another thread to read the data during the writing process. A W mutex has lock policy similar to that of a STD mutex. They are not exactly the same because there are functions to change between read-lock and write-lock mutexes.

An R mutex can change to a W type, and a W mutex can change an R type, provided there is only one level of locking by the thread with the mutex. In the following example, after data is written, the process immediately changes to reading without releasing the lock (thus permitting reading by another thread).

OSMutex mutex;
OS_InitMutex( &mutex );

void write_and_read()
{
 OS_LockMutexW( &mutex ); <- write lock 
 writeData();
 OS_LockMutexFromWToR( &mutex ); <- Change from write lock to read lock
 readData();
 OS_UnlockMutexR(); <- Unlock with a read lock
}

void read()
{
 OS_LockMutexR( &mutex );
 readData();
 OS_UnlockMutexR( &mutex );
}


thread1()
{
 write_and_read();
}

thread2()
{
 read();
}

Mutex Operations

The following operations can be performed on a mutex after it has been initialized.

- Lock the mutex (Lock functions):
OS_LockMutex, OS_LockMutexR, OS_LockMutexW

- Unlock the mutex (Unlock functions):
OS_UnlockMutex, OS_UnlockMutexR, OS_UnlockMutexW, OS_UnlockMutexRW

- Attempt to lock the mutex (TryLock functions):
OS_TryLockMutex, OS_TryLockMutexR, OS_TryLockMutexW

When you lock a mutex, other threads are blocked from locking that mutex. However, read-lock mutexes do not block each other. A blocked thread waits for the mutex to be unlocked.

Lock functions wait until the lock is completed, but the TryLock functions return immediately, regardless of whether the mutex can be locked. You can determine from the returned value whether the lock was successful.

The following operations are available for read-lock and write-lock mutexes.

- Change the mutex type:
OS_LockMutexFromRToW, OS_LockMutexFromWToR

- Attempt to change the mutex type:
OS_TryLockMutexFromRToW, OS_TryLockMutexFromWToR

Internal Operations: Locking a Mutex

This section explains the internal operations when a mutex is locked.

When the mutex is initialized by the OS_InitMutex function, the owner of the OSMutex thread, the count value, the thread type, and the thread queue have the values shown in the following table. (The count member of OSMutex contains two pieces of information: the count value and the mutex type. This information is shown as count and type.)

This section explains locking with a standard mutex, not with a read-lock or write-lock mutex.

If the OS_LockMutex function is called in a program and the designated mutex is not being used in a lock at that point in time, OS_LockMutex records and locks the current thread.

If a OS_LockMutex call occurs for an already locked mutex, the mutex count is incremented, and the mutex is bypassed if it is being locked by the same thread as the current thread. If the thread is not the same, it goes into a pause state until the lock is released. Thread rescheduling occurs at this time.

For example, consider the figure below when there is a Mutex1 and a program that uses it for locking.

thread1 attempts to execute this program. thread1 executes the mutex lock function OS_LockMutex( &Mutex1 ), but because Mutex1 has not yet been used (determined to be true if its owner thread is NULL), thread1 is recorded as the Mutex1 owner thread, the count is set to 1, and the program returns from the lock function.

Next, consider what happens when a separate thread, thread2, attempts to execute the same program. thread2 runs OS_LockMutex( &Mutex1 ), but Mutex1 is already being used in a lock. Because thread2 is not the mutex1 owner thread, thread2 cannot proceed any further. As a result, thread2 goes into a pause state. At this point, thread2 is registered inside the mutex1 thread queue as waiting for this lock to be released.

Now consider a case where thread1 once again runs OSLockMutex( &Mutex1 ) (on the same line or a different line). mutex1 is already being used in a lock, but the mutex1 owner thread is the same as the current thread (thread1), so it can be bypassed. When this happens, the mutex1 count increments.

There are also times when multiple threads are registered in a single mutex thread queue. These threads are all blocked by that mutex. However, it is not necessarily the case that all threads are in a pause state on the same line. If the mutex is unlocked, all registered threads go into an executable state.

Internal Operations: Unlocking a Mutex

This section explains the internal operations when a mutex lock is being released (unlocked). Here, too, the explanation is in regards to a normal mutex and not a read-lock or write-lock mutex.

The function for unlocking with mutex is OS_UnlockMutex. However, calling this function does not invariably unlock the mutex. Only when the count value inside the mutex has decremented to 0 is the lock released. If the value is not zero, count is simply decremented, and control returns from the function.

For example, consider the case in the figure below where thread1 runs OS_UnlockMutex( &Mutex1 ) while using Mutex1 to block thread2.

At this time, the count has been decremented to zero, so the thread registered in the thread queue is put into an executable state and is rescheduled. In this case, thread2 goes into an executable state.

If thread2 has higher priority than thread1, the threads are switched. In other words, thread1 pauses, and thread2 executes. In attempting to execute OS_LockMutex( &Mutex1 ), thread2 was blocked by thread1. But this time, because Mutex1 is released and initialized, thread2 uses Mutex1 to block the other threads.


When thread1 runs the OS_UnlockMutex function and the count value is not zero after being decremented, it can be considered that there are other locks that have not been unlocked, and this can be bypassed. If this is the case, no thread rescheduling occurs. In this way, mutex locks can take on a nested structure.

Attempting to Lock a Mutex

Until there is a lock, OS_LockMutex does not return from the function. On the other hand, OS_TryLockMutex is a function that locks if it can, but that returns immediately if it cannot lock.

With the OS_TryLockMutex function, you can learn from its return value whether the lock was a success.

These other try functions have been prepared in the same way.

- For OS_LockMutexR, there is the OS_TryLockMutexR function

- For OS_LockMutexW, there is the OS_TryLockMutexW function

- For OS_LockMutexFromRToW, there is the OS_TryLockMutexFromRToW function

- For OS_LockMutexFromWToR, there is the OS_TryLockMutexFromWToR function

When the Thread Ends

When the thread ends, every mutex locking that thread is unlocked. This is true not only for normal mutexes, but also for read-lock and write-lock mutexes.

Warning Messages

When a function specifies a pointer to a mutex that is NULL or the call is clearly unusual in some other way, the DEBUG build catches on the SDK_ASSERT function. If an unlock function is called for a mutex that has not been locked, "Illegal unlock mutex" appears. If this message appears, verify that the lock and unlock levels accurately correspond. Note that this message is output by the SDK_TWarning function, so the program does not stop.

See Also

Overview of OS Functions (Exclusion Control)

Revision History

2009/03/11 Standardized terminology for "lock" and "block." Revised some of the text in the figures.
2008/12/17 Revised contents to take read- and write-lock into account.
2004/12/14 Revised terminology and word endings.
2004/11/11 Initial version.


CONFIDENTIAL