Introduction
The code that I'll be presenting is really simple, but I found it quite useful.
Background
Not on rare occasions, I had to deal with multi-threading access to resources; that meant locking the resources somehow, either with a mutex or a simple bool that was acting like a semaphore. Thread safety is easy to implement, but... it can be easier!
Usually, you will directly use the functionality that the Win32 API provides through InitializeCriticalSection, EnterCriticalSection, TryEnterCriticalSection, LeaveCriticalSection, and DeleteCritcalSection. With this, you are in control! But you know, "with great power comes great responsibility", so most probably you'll face the same problems I did when it comes to writing medium-sized thread safe classes, especially if some of the methods are quite big and with multiple return points.
For example:
int MyClass::myMethod()
{
EnterCriticalSection(&cs);
if (something)
{
LeaveCriticalSection(&cs); return -1;
}
else
{
LeaveCriticalSection(&cs); return 1;
}
LeaveCriticalSection(&cs);
return 0;
}
Doesn't seem right, does it?
Using the code
So, how is my piece of code going to improve this? Well, take a look at the following code:
int MyClass::myMethod()
{
scope_lock my_lock(cs);
if (something)
{
return -1;
}
else
{
return 1;
}
return 0;
}
What you additionally need to do is add cs as a property for your class of type CRITICAL_SECTION and initialize it in the constructor with InitializeCriticalSection and release it in the destructor with DeleteCriticalSection, like you would normally do.
For my classes, I also use a macro, something like this:
#ifdef THREAD_SAFE_MY_CLASS
# include "windows.h"
# include "scope_lock.h"
# define LOCK_MY_CLASS scope_lock my_scope_lock(cs);
#else
# define LOCK_MY_CLASS ;
#endif
This allows me to just write LOCK_MY_CLASS in the beginning of my functions, and that's it; if I want to compile the class with multithread support, I just define THREAD_SAFE_MY_CLASS; if not, I just leave it like it is (MY_CLASS being just a custom name).
For the record, you also have scope_lock::release(); to release the mutex sooner and scope_lock::engage(); to take it back.
Playing in the global scope
Here's a sample on playing with out it in the global scope:
#include <iostream>
#include "windows.h"
#include "process.h"
using namespace std;
int z;
void th1(void *)
{
for (int i = 0; i < 10; ++i)
cout << "th1" << endl;
z--;
}
void th2(void *)
{
for (int i = 0; i < 10; ++i)
cout << "th2" << endl;
z--;
}
int main(int argc, char *argv[])
{
z = 2;
_beginthread(th1, 0, 0);
_beginthread(th2, 0, 0);
while (z != 0) Sleep(100);
return 0;
}
And here's the output on my system:
th1th2
th2th1
th2
th2th1
th2th1
th2th1
th2th1
th2th1
th2th1
th2th1
th1
Now, replacing this:
for (int i = 0; i < 10; ++i)
cout << "th2" << endl;
with:
for (int i = 0; i < 10; ++i)
{
scope_lock x(critical_cout);
cout << "th2" << endl;
}
and uncommenting the cs declaration, the initialization, and the deletion, we get the following result:
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
th1
th2
Well, that's it. An example using a class, including this example, is found at the top of the page for download. Note the fact that you need to override the copy and the assign constructor for the class if you're going to use them, since you don't want to copy the CRITICAL_SECTION structure (which will get destroyed twice anyhow and will raise an exception).
How it works
The concept behind this is really simple, it makes use of C++ :) We're just entering the critical section in the constructor and leaving it in the destructor.
Hope it helps.