Click to See Complete Forum and Search --> : CMutex question
Trilobyte
May 10th, 2004, 08:10 AM
I have a class derived from CWinThread called CTestThread. The CTestThread has a member variable called "m_vList", whith is an std::vector<int>. The CTestThread is created in the CChildView constructor.
The thread adds or removes values when it receives a specific message. Now I want to write a function called "int GetInt(int iPos)", which returns the int at the specified position.
when I write the function like this: return m_vList[pos];, it would not be thread safe, right?? So I should use: CMutex, but I am not sure how CMutex works, can somebody explain to me how the CMutex class works?
Thanx anyway,
Trilobyte
Toot
May 10th, 2004, 08:46 AM
The simplest syncronisation object is a Critical Section. The operating system guarantees that only one thread can enter a critical section at a time. ONE thread creates a critical section then shares it to other threads it wishes to synchronisation with. Each time one of these threads wants to access memory also accessible from the other threads, it first enters the critical section. It is then safe to modify the contents of the memory. The controlling thread then leaves the critical section. Simple :sick:.
To create a critical section (e.g. as a static member of a class):
HANDLE hCritSec = NULL;
::InitializeCriticalSection(&hCritSec);
To destroy a critical section (after all threads are finished with it):
:: DeleteCriticalSection(&hCritSec);
To enter a critical section:
::EnterCriticalSection(&hCritSec)
One problem with thread synchronisation is how long any particular thread holds the synchronisation object for - difficulties occur if particular attention isn't paid to what happens if, say, an exception is thrown - does the synchronisation object get released correctly? I wrote a very small code that can help slightly with this problem, as it automatically leaves its contained critical section when it goes out of scope:
#ifdef _DEBUG
#define LOCK_CREATE_ARGS(cs, debug_text) &cs, debug_text
#else
#define LOCK_CREATE_ARGS(cs, debug_text) &cs
#endif
// CLockCriticalSection class declaration.
class CLockCriticalSection
{
// Methods.
public:
// Construct the object with a valid critical section, which is immediately locked.
CLockCriticalSection(PCRITICAL_SECTION pCriticalSection) : m_pCriticalSection(pCriticalSection), m_strFunctionName(_T(""))
{ ASSERT(m_pCriticalSection != NULL); ::EnterCriticalSection(m_pCriticalSection); }
#ifdef _DEBUG
// Construct the object with a valid crit sec to lock, and some debugging info
CLockCriticalSection(PCRITICAL_SECTION pCriticalSection, LPCTSTR ptszFunctionName): m_pCriticalSection(pCriticalSection), m_strFunctionName(_T(""))
{
ASSERT(m_pCriticalSection != NULL);
// enter crit sec
::EnterCriticalSection(m_pCriticalSection);
// copy function name
if (ptszFunctionName)
{
m_strFunctionName = ptszFunctionName;
// report
ATLTRACE(_T("-> Entered critical section on thread 0x%03x in function \"%s\"\n"), ::GetCurrentThreadId(), ptszFunctionName);
}
}
#endif
// Destruct the object, releasing the lock.
~CLockCriticalSection()
{
::LeaveCriticalSection(m_pCriticalSection);
#ifdef _DEBUG
if (!m_strFunctionName.IsEmpty())
{
USES_CONVERSION;
ATLTRACE(_T("<- Left critical section on thread 0x%03x in function \"%s\"\n"), ::GetCurrentThreadId(), (LPCTSTR)m_strFunctionName);
}
#endif
}
// Variables.
private:
// Pointer to the critical section that is locked for the lifetime of this object.
LPCRITICAL_SECTION m_pCriticalSection;
CString m_strFunctionName;
};
Mutexes are a bit more flexible but also a bit more resourceful... generally I tend to get by with critical sections if possible. Well, actually, I try to get by with the ::InterlockedExchange family but they're exceptionally limited :wave:.
Or are you specifically interested in Mutexes?
T
kirants
May 10th, 2004, 12:57 PM
Just wanted to add:
Critical sections work only within process( Note that Initialize/Enter/LeaveCri... take a pointer to structure which is valid only within a process's address space).
Mutexs are kernel objects used for synchro across process boundaries as long as they are named.
So, you have one factor which could decide what you want.
NigelQ
May 11th, 2004, 12:30 AM
In addition to the other correct answers, but in addition to toot's approach, the possibility of creating a deadlock as he described can be avoided by using a CSingleLock object on the critical section (created and destroyed as he described).
The difference with a CSingleLock object is that it releases (unlocks) the critical section whenever it passes out of scope, either when it has programmatically finished, or if an exception occurs, or if the programmer simply forgot and returned from the function early without releasing it.
Hope this helps,
- Nigel
Toot
May 11th, 2004, 04:29 AM
Well, it isn't going to stop deadlocks but - as you say - it will help with automatic unlocking when it goes out of scope. Deadlock avoidance is a skill of its own :D.
T
Trilobyte
May 11th, 2004, 10:31 AM
Thanx a lot guys, I think I am going to use CCriticalSection since it is easier to use.
Trilobyte
May 11th, 2004, 11:50 AM
Just wanted to make I understand it,
In the following code m_Critical(CCritical) and m_vInt(std::vector<int>) are members of the CTestThread program.
If I want to add an int to the list I send a message to the Thread using PostThreadMessage(WM_APP+1, (WPARAM)TheInt, (LPARAM)1);.
If I want to read an int from the list I call m_TestThread->Get_Int(iIndex);.
Is this code fully thread safe?
void CTestThread::OnThreadMessage(WPARAM wParam, LPARAM lParam)
{
if (lParam == 1)
{
m_Critical.Lock();
m_vInt.push_back((int)wParam);
m_Critical.Unlock();
}
}
int CTestThread::Get_Int(int iIndex)
{
m_Critical.Lock();
if (iIndex < 0)
return -1;
if (iIndex > m_vInt.size())
return -1;
return m_vInt[iIndex];
m_Critical.Unlock();
}
Toot
May 11th, 2004, 12:05 PM
No almost certainly not - look at Get_Int() - you lock the critical section then return from the function without unlocking. However m_Critical is declared, it will not have gone out of scope after the function so the critical section object will still be locked. You might be lucky as it might be this thread that next tries to lock the critical section (which will succeed) but you're asking for trouble there.
When you're up against synchronisation issues like this it's a good idea to have just one function exit point - that will start to make life a little easier. Make sure that for every path through the code where you lock a critical section, there is a corresponding unlock.
That's why I offered the CLockCriticalSection object - as in this case you could have said
Declaration
HANDLE m_hCriticalSection;
Example - Get_Int()
int CTestThread::Get_Int(int iIndex)
{
CLockCriticalSection(&m_hCriticalSection);
// now we're covered for all these exit points (and an exception)
if (iIndex < 0)
return -1;
if (iIndex > m_vInt.size())
return -1;
return m_vInt[iIndex];
}
BTW, don't forget about creating and deleting the critical section object, as described previously.
HTH,
T
Trilobyte
May 11th, 2004, 12:24 PM
:blush: :blush: :blush: :blush: You must think I am a newbie, I broke the number one law in programming: Never put code after a return.
I redesigned the function:
int CTestThread::Get_Int(int iIndex)
{
int retVal;
m_Critical.Lock();
if (iIndex < 0 || iIndex > m_vInt.size())
retVal = -1;
else
retVal = m_vInt[iIndex];
m_Critical.Unlock();
return retVal;
}
codeguru.com
Copyright WebMediaBrands Inc., All Rights Reserved.