Mutex

Files CCore/inc/task/Mutex.h CCore/src/task/Mutex.cpp

Mutex is a mutex, i.e. a class, used to serialize a group of code blocks, executed by multiple threads.


class Mutex : NoCopy
 {
   ....
   
  public: 
  
   Mutex();
   
   explicit Mutex(TextLabel name);
   
   ~Mutex();
   
   void lock();
  
   void unlock();

   using Lock = LockObject<Mutex> ;
 };

lock() establishes an exclusive ownership on the mutex from the calling thread. A thread can lock the mutex multiple times, any extra locks just increases the internal lock counter.

If the mutex is locked and a different thread is trying to lock it, then this thread will be blocked, until the mutex is released.

unlock() is a "memory fence" operation, i.e. all variable changes, made before unlock, will be visible to other threads, locking the mutex.

unlock() is used to release a lock. If the mutex was locked several times, it must be unlocked the same number of times to be completely released.

It is highly recommended to lock a mutex using the inner class Lock:


Mutex mutex;

 {
  Mutex::Lock lock(mutex);

  ....
 }

A manual calls of the lock()/unlock() must be excluded.

Mutex operations are efficient, if they don't cause a task block/release.

The most common Mutex usage is to make a class thread-safe:


class SomeClass
 {
   Mutex mutex;

  public:

   void method()
    {
     Mutex::Lock lock(mutex);

     ....
    }
 };

Building a large-scale application, comprised by multiple components, it is easy to create a sadden deadlock using mutexing in a non-systematic way. So to avoid it I recommend to follow these simple rules:

Lock-free context is a part of code which does not hold any mutex. Most of application code must run in a lock-free context. If you locked a mutex, don't use any other synchronization methods, both blocking and releasing. Make locked sections of code "quick". Avoid nested mutex locks. Sometimes you have to nest mutexes, for example, you may call a memory allocation function, which holds a heap mutex. But in this case there is no risk to create a deadlock. "Leaf" mutexes, i.e. mutexes whose locked code regions do not make another locks, are deadlock safe. So keep mutexes "leaf", restrict particular mutex usage by defined code regions, for example, methods of a single class. If you still have to nest mutex usage, decide an order of locks: one mutex always must be locked before another.

HCore Mutex


inline unsigned MutexSpinCount() { return Sys::GetSpinCount(); }

class Mutex : NoCopy
 {
   ....

  public: 
  
   explicit Mutex(unsigned spin_count=MutexSpinCount());
   
   explicit Mutex(TextLabel name,unsigned spin_count=MutexSpinCount());
   
   ~Mutex();
   
   void lock();
  
   void unlock();
  
   unsigned getSemCount();
   
   using Lock = LockObject<Mutex> ;
 };

HCore Mutex constructor uses the additional argument: spin_count. It is defaulted to the some target-dependent value. Spinning is used on multi-core systems. If the mutex is locked, then it is useful not to block the thread immediately, but to "spin" some times, waiting for the mutex release.

getSemCount() method can be used for a performance investigations. It is the number of times, when the mutex called the internal system semaphore.

XCore mutex


class Mutex : NoCopy
 {
   ....
   
  public: 
  
   Mutex();
   
   explicit Mutex(TextLabel name);
   
   ~Mutex();
   
   TextLabel getName() const;

   void lock();
  
   void unlock();
  
   using Lock = LockObject<Mutex> ;
 };

XCore Mutex implementation has two important properties.

First, it has a mutex deadlock detection. If such situation has happened, then the Abort() with the proper diagnostic message is called.

Second, the priority inheritance is used to boost task priorities. Normally, each task runs on the given priority. But in some situations the task priority is temporary boosted. Consider the following case. Task A has locked a mutex. Task B with higher priority is trying to lock the same mutex. But because it is locked, task B becomes blocked, until task A release the mutex. In this case the priority of task A is boosted to the priority of task B.