Mutex and Locks

Metrowerks::threads has 6 types of mutexes.

Note that each mutex type has only a default constructor and destructor. It is not copyable, and it does not have lock and unlock functions. You access this functionality via one of the nested types:

You can use the scoped_lock to lock and unlock the associated mutex, and test whether it is locked or not (the operator bool_type is just a safe way to test the lock in an if statement like you might a pointer), for example:

if (my_lock) ...

Normally you won't use any of the scoped_lock's members except it's constructor and destructor. These lock and unlock the mutex respectively.

Listing: Example of lock and unlock usage
#include <ewl_thread>
Metrowerks::mutex foo_mut;

void foo()
{
    Metrowerks::mutex::scoped_lock lock(foo_mut);
    // only one thread can enter here at a time
}   // foo_mut is implicitly unlocked here, no matter how foo returns

In single thread mode, the above example compiles, and the lock simply doesn't do anything. If you expect foo() to call itself, or to call another function which will lock the same mutex (before foo releases foo_mut), then you should use a recursive mutex.

A mutex can conveniently be a class member, which can then be used to lock various member functions on entry. But recall that your class copy constructor will need to create a fresh mutex when copying, as the mutex itself can not be copied (or assigned to).

In some cases you want to lock the mutex only if you don't have to wait for it. If it is unlocked, you lock it, else your thread can do something else. Use scoped_try_lock for this application. Note that not all mutex types support scoped_try_lock (have it as a nested type). The scoped_try_lock looks just like scoped_lock but adds this member function bool try_lock(),

Listing: Example of try_lock() usage
#include <ewl_thread>
Metrowerks::try_mutex foo_mut;

void foo()
{
    Metrowerks::try_mutex::scoped_try_lock lock(foo_mut, false);
    if (lock.try_lock())
    {
        // got the lock
    }
    else
    {
        // do something else
    }
}

In the above example, the second parameter in the constructor tells the lock to not lock the mutex upon construction (else you might have to wait).

Sometimes you are willing to wait for a mutex lock, but only for so long, and then you want to give up. scoped_timed_lock is the proper lock for this situation. It looks just like a scoped_lock but adds two members:

  bool timed_lock(const universal_time& unv_time);

  bool timed_lock(const elapsed_time& elps_time); 

These let you specify the amount of time you're willing to wait, either in terms of an absolute time ( universal_time), or in terms of an interval from the current time ( elapsed_time).

Listing: Example of timed_lock()
Metrowerks::timed_mutex foo_mut;
void foo()
{
    Metrowerks::timed_mutex::scoped_timed_lock lock(foo_mut, false);
    Metrowerks::elapsed_time time_out(1, 500000000);
    if (lock.timed_lock(time_out))
    {
        // got the lock
    }
    else
    {
        // do something else
    }
}

This specifies that the thread should quit trying for the lock after 1.5 seconds. Both elapsed_time and universal_time are simple structs with sec_ and nsec_ exposed data members representing seconds and nanoseconds. In the case of universal_time, this is the number of seconds and nanoseconds since midnight Jan. 1, 1970. The universal_time default constructor returns the current time. So the above example could have also been written as in Alternate example of timed_lock() usage .

Listing: Alternate example of timed_lock() usage
void foo()
{
    Metrowerks::timed_mutex::scoped_timed_lock lock(foo_mut, false);
    Metrowerks::elapsed_time time_out(1, 500000000);
    Metrowerks::universal_time now;

    if (lock.timed_lock(now + time_out))
    {
        // got the lock
    }
    else
    {
        // do something else
    }
}

In general you can add and subtract and compare universal_time and elapsed_time as makes sense.

In single thread mode, all locks will lock their mutexes and return immediately (times are ignored). However, if you try to lock a lockedmutex, or unlock an unlockedmutex, then an exception of type Metrowerks::lock_error (derived from std::exception) will be thrown (even in single thread mode).