C++ 中的易失性类
我有一个关于 volatile 关键字的问题,我似乎找不到答案。
在我的应用程序中,我有作为线程之间的状态缓冲区共享的数据类,并且我需要从多个线程定期更新它。
类看起来像这样:
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
但编译器抱怨 const_cast 转换。
它几乎看起来像我滥用 volatile 关键字,但同时,我不能让参数在调用之间缓存,因为如果两个或三个线程分配它们,可能会出现问题。
如何用 C++ 编写线程/缓存安全类?
PS:到目前为止,锁定还不是瓶颈,而且锁定几乎都是单行的,因此从性能角度来看,序列化和锁定目前不是问题。当然,如果有更好的算法,我很乐意倾听。
编辑: 我仍然不清楚......
考虑这个例子(内联+链接时间代码生成);
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
}
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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在可移植代码中,
易失性
与多线程完全无关。在 MSVC 中,作为扩展,
易失性
限定的简单本机类型(例如int
)可以与简单的读取和存储操作一起用于原子访问,但这不扩展到读-修改-写访问,例如i++
或类类型的对象,例如std::string
。为了确保从多个线程安全访问
m_strParam
变量,每个线程必须在访问m_strParam
之前锁定关联的互斥锁,然后再解锁。锁定/解锁将确保每个线程都能看到 m_strParam 的正确值——如果另一个线程通过下一个锁定修改了该变量,则不会对内部指针进行任何缓存。如果正确使用锁,则不需要
volatile
限定符,也不需要使用const_cast
。In portable code,
volatile
has absolutely nothing to do with multithreading.In MSVC, as an extension,
volatile
-qualified simple native types such asint
can 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_strParam
variable 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_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 useconst_cast
.您应该
const_cast
strIn 而不是 m_strParam:C++ 中没有关于线程/缓存安全类的普遍接受的习惯用法,因为当前流行的 C++ 标准对并发编程保持沉默。
易失性
不保证任何原子性,并且在编写可移植的、线程安全的程序时毫无用处。请参阅以下链接:You should
const_cast
strIn not m_strParam: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:std::string
(和std::wstring
)并不是设计为易失性的,我建议您不要以这种方式使用它。确保线程安全的常用方法是在需要时使用互斥体、信号量并避免使用全局变量。
正如 Marcus Lindblom 提到的,锁定机制中通常有读/写栅栏,负责处理潜在的缓存问题。所以你应该是安全的。
std::string
(andstd::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.
无需使用
易失性
。 编译器内存屏障应该足够了。不过,仍然需要Lock
/Unlock
。也就是说,尽管在某些编译器上,当应用于诸如 wstring 之类的非基本类型时,
volatile
关键字与内存屏障具有相同的含义。恰当的例子:VC++No need to use
volatile
. A compiler memory barrier should be sufficient. TheLock
/Unlock
is still needed, though. That is,Although on some compilers, the
volatile
keyword has the same meaning as memory barrier when applied to non-primitive types likewstring
. Case in point: VC++