⬆️ ⬇️

Multithreading, general data and mutexes

Introduction



When writing multi-threaded applications it is almost always necessary to work with shared data, the simultaneous change of which can lead to very unpleasant consequences.



To block shared data from simultaneous access, you must use synchronization objects.



In this topic, the method of working with mutexes is considered, which significantly reduces the number of potential errors associated with creation / deletion and capture / release.

')

Failure of the mutex causes a memory leak, non-capture leads to incorrect data, and non-release blocks all functions working with shared data.



Below we consider working with mutexes in Windows and Unix, a similar idea can be used when working with other synchronization objects.



This idea is a special case of the “Resource Allocation - Is Initialization (RAII)” method.







Create, configure and delete mutex



First, let's declare the class CAutoMutex, which creates a mutex in the constructor and deletes it in the destroyer.

Pros:

- it is not necessary to produce for the whole project similar fragments initialization codes, settings and deletion of mutex

- automatic removal of mutex and release of resources occupied by it



// -, (Windows)

class CAutoMutex

{

//

HANDLE m_h_mutex;



//

CAutoMutex( const CAutoMutex&);

CAutoMutex& operator =( const CAutoMutex&);



public :

CAutoMutex()

{

m_h_mutex = CreateMutex(NULL, FALSE, NULL);

assert(m_h_mutex);

}



~CAutoMutex() { CloseHandle(m_h_mutex); }



HANDLE get () { return m_h_mutex; }

};



* This source code was highlighted with Source Code Highlighter .




On Windows, mutexes are recursive by default, but not on Unix. If the mutex is not recursive, then an attempt to capture it twice in one thread will result in deadlock.

To create a recursive mutex in Unix, you need to set the appropriate flag during initialization. The corresponding CAutoMutex class would look like this (checks for return values ​​are not shown for compactness):

// -, (Unix)

class CAutoMutex

{

pthread_mutex_t m_mutex;



CAutoMutex( const CAutoMutex&);

CAutoMutex& operator =( const CAutoMutex&);



public :

CAutoMutex()

{

pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex_init(&m_mutex, &attr);

pthread_mutexattr_destroy(&attr);

}

~CAutoMutex()

{

pthread_mutex_destroy(&m_mutex);

}

pthread_mutex_t& get ()

{

return m_mutex;

}

};




* This source code was highlighted with Source Code Highlighter .




Capture and release mutex



By analogy with the previous class, we will declare the CMutexLock class, which occupies the mutex in the constructor and frees in the destructor. The created object of this class will automatically capture the mutex and release it at the end of the scope, regardless of what kind of exit it was: normal exit, premature return, or throwing an exception. The advantage is also that you can not produce similar fragments of the code for working with mutexes.



// -,

class CMutexLock

{

HANDLE m_mutex;



//

CMutexLock( const CMutexLock&);

CMutexLock& operator =( const CMutexLock&);

public :

//

CMutexLock(HANDLE mutex): m_mutex(mutex)

{

const DWORD res = WaitForSingleObject(m_mutex, INFINITE);

assert(res == WAIT_OBJECT_0);

}

//

~CMutexLock()

{

const BOOL res = ReleaseMutex(m_mutex);

assert(res);

}

};



* This source code was highlighted with Source Code Highlighter .




For even more convenience, we will declare the following macro:



// ,

#define SCOPE_LOCK_MUTEX(hMutex) CMutexLock _tmp_mtx_capt(hMutex);



* This source code was highlighted with Source Code Highlighter .




The macro allows you not to keep in mind the name of the CMutexLock class and its namespace, and also not to puzzle over the name of the object being created (for example, _tmp_mtx_capt).



Examples of using



Consider usage examples.



To simplify the example, let's declare a mutex and general data in the global scope:

//

static CAutoMutex g_mutex;



//

static DWORD g_common_cnt = 0;

static DWORD g_common_cnt_ex = 0;



* This source code was highlighted with Source Code Highlighter .




An example of a simple function that uses common data and the macro SCOPE_LOCK_MUTEX:

void do_sth_1( ) throw ()

{

// ...

//

// ...



{

//

SCOPE_LOCK_MUTEX(g_mutex. get ());



//

g_common_cnt_ex = 0;

g_common_cnt = 0;



//

}



// ...

//

// ...

}



* This source code was highlighted with Source Code Highlighter .




Isn't it true that the do_sth_1 () function looks more elegant than the next one? do_sth_1_eq:

void do_sth_1_eq( ) throw ()

{

//

if (WaitForSingleObject(g_mutex. get (), INFINITE) == WAIT_OBJECT_0)

{

//

g_common_cnt_ex = 0;

g_common_cnt = 0;



//

ReleaseMutex(g_mutex. get ());

}

else

{

assert(0);

}

}




* This source code was highlighted with Source Code Highlighter .




In the following example, there are three exit points from the function, but there is only one mention of mutex (declaration of the mutex blocking area):

// -

struct Ex {};



// ,

int do_sth_2( const int data ) throw (Ex)

{

// ...

//

// ...



//

SCOPE_LOCK_MUTEX(g_mutex. get ());



int rem = data % 3;



if (rem == 1)

{

g_common_cnt_ex++;

//

throw Ex();

}

else if (rem == 2)

{

//

g_common_cnt++;

return 1;

}



//

return 0;

}



* This source code was highlighted with Source Code Highlighter .




Note: I am not in favor of using multiple return s in one function, just an example of this.

becomes a little more revealing.

And if the function were longer and the points of exclusion would be a dozen? Without a macro, it was necessary to put ReleaseMutex (...) in front of each of them, and you can make a mistake here very easily.



Conclusion



The examples of classes and macros are simple enough; they do not contain complex checks and expect the release of the mutex for infinite time. But even this makes life easier in many cases. And if it makes life easier, why not use it?



UPD: The first class of CAutoMutex was not written by mistake, instead it was re-declared the second class of CMutexLock. Fixed.



UPD2: Removed inline words in the declaration of methods inside classes as unnecessary.



UPD3: A variant of the CAutoMutex class with a recursive mutex for Unix has been added.



UPD4: Moved to C ++ Blog

Source: https://habr.com/ru/post/72929/



All Articles