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, volatile
has absolutely nothing to do with multithreading.
In MSVC, as an extension, volatile
-qualified simple native types such as int
can be used with simple read and store operations for atomic accesses, but this does not extend to read-modify-write accesses such as i++
or to objects of class type such as std::string
.
To ensure safe access to your m_strParam
variable from multiple threads, each thread must lock the associated mutex before accessing m_strParam
, and unlock it afterwards. The lock/unlock will ensure that the correct value of m_strParam
is 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 volatile
qualifier, nor do you need to use const_cast
.
You should const_cast
strIn not m_strParam:
m_strParam = const_cast<wstring> (strIn);
There are no generally accepted idioms about thread/cache safe classes in C++, as the current prevalent C++ standard is silent about concurrent programming. volatile
does not make any guarantees of atomicity and is useless in writing portable, thread-safe programs. See these links:
- Should volatile Acquire Atomicity and Thread Visibility Semantics?
- Volatile: Almost Useless for Multi-Threaded Programming
std::string
(and std::wstring
) wasn't designed to be volatile and I would advise you against using it that way.
The usual way to ensure thread-safety is to use mutexes, semaphores, where needed and to avoid the use of global variables.
And as Marcus Lindblom mention, there are usually read/write fence in the lock mechanisms that take care of dealing with potential caching issues. So you should be safe.
No need to use volatile
. A compiler memory barrier should be sufficient. The Lock
/Unlock
is still needed, though. That is,
class CBuffer
{
//Constructor, destructor, Critical section initialization/destruction ...
wstring m_strParam;
};
void CBuffer::InterlockedParamSetter(const wstring &strIn)
{
Lock();
//compiler-specific memory barrier;
m_strParam = strIn;
//compiler-specific memory barrier;
Unlock();
}
Although on some compilers, the volatile
keyword has the same meaning as memory barrier when applied to non-primitive types like wstring
. Case in point: VC++
Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword. However, a memory barrier is more efficient because ...
精彩评论