Until now, you were told that all class methods should access/update their members
in a thread safe manner, so like the rest of us sheep, you created a member variable:
CCriticalSection mCritSec;
and wrapped every class method with:
CSingleLock mSingleLock(&mCritSec);
if(!mSingleLock.Lock(timeout))
{
// log some error
return E_FAIL;
}
In a really complex multithreaded application, this causes EXCLUSIVE access to that
class, even if the method does not update any member variables! This results in a
class implementation that only allows SERIAL access, not really what was meant by
the multithreading capabilities of OO design.
Indeed, locking is needed for read-only methods that use items such as STL iterators,
whereas a call to a destructive class method from another thread may render that
iterator invalid.
What was really needed was a way for you to allow multiple threads CONCURRENT READ
access to the class, and EXCLUSIVE WRITE...
In your class definition, insert this line prefereably at the top of bottom of the class:
(because it declares a scope of public)
CONCUR_DECLARE_LOCKABLE;
To obtain a resource for ANY LOCKABLE class object in you application code,
CONCUR_DECLARE_RESOURCE_LOCK(object,"any debug string");
to access a resource in a NON-DESTRUCTIVE manner
if( !CONCUR_RESOURCE_READ_LOCK(object,timeout) )
{
// log some error
}
to access a resource in a DESTRUCTIVE manner
if( !CONCUR_RESOURCE_WRITE_LOCK(object,timeout) )
{
// log some error
}
to relinqish access to the resource
CONCUR_RESOURCE_UNLOCK(object);
To allow locking of specific elements in a class, insert this line prefereably after the element definition:
CONCUR_DECLARE_ELEMENT_LOCKABLE(element-name);
To obtain a resource for ANY LOCKABLE class object in you application code,
CONCUR_DECLARE_ELEMENT_LOCK(object,element-name,"any debug string");
to access a resource in a NON-DESTRUCTIVE manner
if( !CONCUR_ELEMENT_READ_LOCK(object,element-name,timeout) )
{
// log some error
}
to access a resource in a DESTRUCTIVE manner
if( !CONCUR_ELEMENT_WRITE_LOCK(object,element-name,timeout) )
{
// log some error
}
to relinqish access to the resource
CONCUR_ELEMENT_UNLOCK(object,element-name);
1) start with read access immediately before referencing a member,
2) ask for write access immediately before updating member,
3) relinquish control when resource is no longer referenced
ConcurrentLock.h comes with the following macros already defined:
// for class locking
zzz_DECLARE_LOCKABLE
zzz_DECLARE_RESOURCE_LOCK(o,desc)
zzz_RESOURCE_READ_LOCK(o,timeout)
zzz_RESOURCE_WRITE_LOCK(o,timeout)
zzz_RESOURCE_DELETE_LOCK(o,timeout)
zzz_RESOURCE_UNLOCK(o)
// for element locking
zzz_DECLARE_ELEMENT_LOCKABLE(e)
zzz_DECLARE_ELEMENT_LOCK(o,e,desc)
zzz_ELEMENT_READ_LOCK(o,e,timeout)
zzz_ELEMENT_WRITE_LOCK(o,e,timeout)
zzz_ELEMENT_DELETE_LOCK(o,e,timeout)
zzz_ELEMENT_UNLOCK(o,e)
where zzz stands for any of the following locking mechanisms:
CONCUR ConcurrentLock
MUTEX Standard Mutex
CRITSECT Critical Section
NOOP No locking
This makes it easier on the application developer to create logic that can be tweaked
later to accommodate the appropriate locking mechanism.
example.hpp
#include <list>
#include <iterator>
typedef unsigned long _t_node;
typedef list<_t_node*> _t_list;
class example
{
CONCUR_DECLARE_LOCKABLE;
public:
example();
~example();
private:
_t_list someList;
public:
DWORD findElement(_t_node* someElement);
};
example.cpp
DWORD example::findElement(_t_node* someElement)
{
// 1) start with read access immediately before referencing a member
CONCUR_DECLARE_RESOURCE_LOCK(this,"example function");
if( !CONCUR_RESOURCE_READ_LOCK(this,timeout) )
{
// log some error
// don't forget to cleanup
return E_FAIL;
}
some_iterator iter;
for ( iter = someList.begin(); iter != someList.end(); iter ++ )
{
if ( (*iter) == someElement )
{
// 2) ask for write access immediately before updating member,
if( !CONCUR_RESOURCE_WRITE_LOCK(this,timeout) )
{
// log some error
// don't forget to cleanup
return E_FAIL;
}
someList.erase(iter);
// !!! since list is modified, leave the loop !!!
break;
}
}
// 3) relinquish control when resource is no longer referenced
CONCUR_RESOURCE_UNLOCK(this);
return S_OK;
}
Imagine, if you will, that the ladies' room in your office is broken, and everyone
must wait in line for using the men's room (which has many urinals, but only one
stall with a toilet).
Many men can use the urinals at the same time, but if a woman wants to use the
toilet, she must wait for all the men to finish their business and leave the room.
Any remaining men in line must wait for any woman occupying the bathroom to finish
her business and leave the room.
Since there is only one toilet, each woman must wait for the room to be empty before
entering.
There are some special conditions though:
If a man wants to use the urinal, and the only people in there are family members
(resources from the same thread), he can enter.
If a man wants to use the toilet, he must go back in line (although he is allowed to
be placed in front) and wait for everyone else to leave.
Every use of urinal represents READONLY access, and every use of the toilet represents
WRITEABLE access to the resource.
(Draw it on paper, it works.)