I have a question concerning volatile keyword I can’t seem to find an answer for.
In my app I have data class that is shared as a state buffer between threads, and I need it to be updated regularly from multiple threads.
Class looks like this:
class CBuffer
{
//Constructor, destructor, Critical section initialization/destruction
//...
volatile wstring m_strParam;
//...
void InterlockedParamSetter(const wstring &strIn);
wstring InterlockedParamGetter();
ParamSetter(const wstring &strIn);
wstring ParamGetter();
Lock();
Unlock();
}
void CBuffer::InterlockedParamSetter(const wstring &strIn)
{
Lock();
const_cast<wstring>(m_strParam) = strIn;
Unlock();
}
//... other f-ns
But the compiler complains about const_cast conversion.
It almost looks like I’m abusing volatile keyword, but at the same time, I can’t let the params to be cached between the calls, because if two or three threads will assign them, something can go wrong.
How do you write thread/cache safe classes in C++?
P.S.: So far locking is not the bottleneck, and the locks are pretty much single-liners, so for now serialization and locking is not an issue from performance standpoint. Of course, if there is a better algorithm, I will gladly listen.
EDIT:
I’m still unclear…
Consider this example (inlining + link time codegen);
void Thread1Func()
{
//Unrolled, inlined InterlockedParamSetter()
EnterCriticalSection(&cs);
WriteTo(CBuffer::m_strParam);//write to buffer, wstring not volatile, cache it
LeavCriticalSection(&cs);
//Unroll end
//DoSomethingElse
//!!!!Thread 2 does InterlockedParamSetter
//which causes wstring::reserve and invalidates old pointers!!!!
//Unrolled, inlined InterlockedParamSetter()
EnterCriticalSection(&cs);
WriteTo(CBuffer::m_strParam);//oh, good, we have a pointer to old buffer
//cached in one of the registers, write to it -> access violation
LeavCriticalSection(&cs);
//Unroll end
}
In portable code,
volatilehas absolutely nothing to do with multithreading.In MSVC, as an extension,
volatile-qualified simple native types such asintcan be used with simple read and store operations for atomic accesses, but this does not extend to read-modify-write accesses such asi++or to objects of class type such asstd::string.To ensure safe access to your
m_strParamvariable from multiple threads, each thread must lock the associated mutex before accessingm_strParam, and unlock it afterwards. The lock/unlock will ensure that the correct value ofm_strParamis seen by each thread — there will not be any caching of the internal pointer if another thread has modified the variable by the next lock.If you use locks correctly, you do not need the
volatilequalifier, nor do you need to useconst_cast.