开发者

Volatile classes in C++

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 ...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜